지난 파이썬 반복자(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 구문으로 좀 더 비동기적으로 만들 수 있다. 이 부분은 다음기회에!
'프로그래밍' 카테고리의 다른 글
macOS 터미널 녹화하기 - asciinema 와 agg 활용 (5) | 2025.02.12 |
---|---|
파이썬 반복자(iterator)와 발생자(generator) (3) | 2025.01.30 |
아이폰 단축어로 그날의 모든것 자동으로 일기쓰기 (1) | 2025.01.25 |
X API v2로 오늘의 내 트윗들을 가져오는 아이폰 단축어 (6) | 2025.01.17 |