프로그래밍

파이썬 발생자(generator)와 코루틴(coroutine)

yong27 2025. 2. 19. 17:33

지난 파이썬 반복자(iterator)와 발생자(generator) 편에서 발생자란 무엇인지, 어떻게 쓰는 것인지 기초를 알아봤다. 발생자는 함수와 유사하지만 특정 시점에 멈춘 후, 필요할 때마다 다음 단계를 진입하여 쓸 수 있다는 특징 때문에, 일반 무한루프를 대신하여 동시성(concurrency) 및 이벤트 기반 프로그래밍에 편리하게 사용된다. 이번 글에서는 발생자의 추가 기능을 더 알아보고, 동시성이 중요한 "코루틴(coroutine)"으로 활용하는 법을 소개한다. 

 

앞편 글에서 봤듯이, 발생자는 함수 안에서 yield 구문을 통해 값을 하나씩 반환하면서 실행을 멈췄다가 다시 시작할 수 있다. 

 

def simple_generator():
    print("시작!")
    yield 1
    print("중간!")
    yield 2
    print("끝!")

 

<실행 결과>

In [1]: gen = simple_generator()

In [2]: print(next(gen))
시작!
1

In [3]: print(next(gen))
중간!
2

In [4]: print(next(gen))
끝!
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Cell In[4], line 1
----> 1 print(next(gen))

StopIteration:

 

발생자에 .send() 메쏘드로 외부에서 값을 전달할 수 있다. 전달한 값은 yield 구문의 대입문 왼쪽 변수에 들어간다. 

 

def message_generator():
    message = "안녕하세요!"
    while True:
        new_message = yield message
        if new_message is not None:
            message = new_message

 

<실행결과>

In [1]: gen = message_generator()

In [2]: print(next(gen))
안녕하세요!

In [3]: print(gen.send("반갑습니다"))
반갑습니다

In [4]: print(gen.send("잘 지내세요?"))
잘 지내세요?

 

발생자에 .close() 메쏘드로 강제로 종료할 수 있다. 

 

def countdown(n):
    while n > 0:
        print(n)
        yield n
        n -= 1

 

<실행결과>

 

In [1]: gen = countdown(5)

In [2]: next(gen)
5
Out[2]: 5

In [3]: next(gen)
4
Out[3]: 4

In [4]: gen.close()

In [5]: next(gen)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Cell In[5], line 1
----> 1 next(gen)

StopIteration:

 

발생자에 .throw() 메쏘드로 원하는 예외를 발생시킬 수 있다. 

def controlled_gen():
    try:
        while True:
            try:
                value = yield
                print(f"Processing: {value}")
            except ValueError:
                print("ValueError caught inside generator!")
    finally:
        print("Generator is closing...")

 

<실행결과>

 

In [1]: gen = controlled_gen()

In [2]: next(gen)  # 초기화

In [3]: gen.send(42)  # "Processing: 42" 출력
Processing: 42

In [4]: gen.throw(ValueError)  # "ValueError caught inside generator!" 출력
ValueError caught inside generator!

In [5]: gen.close()  # "Generator is closing..." 출력
Generator is closing...

 

발생자에서 다른 발생자를 다룰 때, yield from 을 써서 중첩없이 간결하게 코딩할 수 있다. 

 

def sub_generator():
    yield "A"
    yield "B"

def main_generator():
    yield "Start"
    yield from sub_generator()  # sub_generator의 모든 yield를 가져옴
    yield "End"

gen = main_generator()
print(list(gen))  # ['Start', 'A', 'B', 'End']

 

이상의 기능들로 발생자를 코루틴(coroutine)으로 쓸 수 있다. 코루틴은 일반 함수와 달리, 중간에 실행을 멈추고 나중에 다시 재개할 수 있는 함수를 의미한다. 이를 통해 비동기 프로그래밍, 이벤트 기반 프로그래밍, 병렬 작업, 데이터 스트리밍 등에 활용할 수 있다. 

 

def data_processor():
    total = 0
    count = 0
    while True:
        value = yield total / count if count > 0 else 0
        total += value
        count += 1

proc = data_processor()
next(proc)  # 초기화
print(proc.send(10))  # 평균: 10.0
print(proc.send(20))  # 평균: 15.0
print(proc.send(30))  # 평균: 20.0

 

위 코드는 데이터 스트리밍 예시로, send() 메소드를 넣어줄 때마다 해당 값을 추가한 전체 평균 값을 계산한다. 상호반응형(interactive) 환경 혹은 실시간 센서에서 데이터를 받을 때 등등 데이터 분석시 유용하게 쓸 수 있다. 

 

참고로, 코루틴은 파이썬 3.5 이후부터 asyncio, async def, await 구문으로 좀 더 비동기적으로 만들 수 있다. 이 부분은 다음기회에! 

반응형