지난 파이썬 반복자(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 구문으로 좀 더 비동기적으로 만들 수 있다. 이 부분은 다음기회에!