Remarks
이 글은 전문가를 위한 파이썬(Fluent Python)을 정리한 자료입니다.
1. Coroutine
- 기본적인 구현
def simple_coroutine(): print("-> coroutine started") x = yield print("-> coroutine received:", x) my_coro = simple_coroutine() next(my_coro) # -> coroutine started my_coro.send(42) # -> coroutine received: 42 # --------------------------------------------------------------------------- # StopIteration Traceback (most recent call last) # Cell In[4], line 1 # ----> 1 my_coro.send(42) # StopIteration:
- 코루틴은 네 가지 상태를 가진다. 상태는
inspect.getgeneratorstate()
를 통해 알 수 있다.GEN_CREATED
: 대기 상태GEN_RUNNING
: 실행 중인 상태GEN_SUSPENDED
:yield
문에서 대기하고 있는 상태GEN_CLOSED
: 실행이 완료된 상태
- 처음
next(coro)
를 호출해서 코루틴이 호출자로부터 값을 받을 수 있도록 첫yield
문까지 실행을 진행시키는 작업을 priming(기동)한다고 표현한다. - 예제: 이동 평균을 계산하는 코루틴
def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total / count coro = averager() next(coro) for i in range(5): result = coro.send(i) print(result) # 0.0 # 0.5 # 1.0 # 1.5 # 2.0
next(coro)
: 첫yield
까지 실행하고,average
를 생성total = 0.0 count = 0 average = None while True: yield average
coro.send(i)
:yield
문의 값이i
로 평가되고 이 값이term
에 할당된다.term = yield total += term count += 1 average = total / count
그리고 다음
yield
문까지 실행시켜average
를 생성하고 대기(GEN_SUSPENDED
)while True: yield average
- 코루틴을 기동하기 위한 데커레이터
from functools import wraps def coroutine(func): @wraps(func) def primer(*args, **kwargs): gen = func(*args, **kwargs) next(gen) return gen return primer
next()
가 실행된 코루틴이 반환되기 때문에GEN_SUSPENDED
상태로 나오게 된다. - 코루틴 종료와 예외 처리
class DemoException(Exception): pass def demo_exc_handling(): print("-> coroutine started") while True: try: x = yield except DemoException: print("*** DemoException handled. Continuing...") else: print("-> coroutine received: {!r}".format(x)) raise RuntimeError("This line should never run.") exc_coro = demo_exc_handling() # GEN_CREATED next(exc_coro) # GEN_SUSPENDED exc_coro.throw(DemoException) # GEN_SUSPENDED # exc_coro.throw(Exceotion) # GEN_CLOSED exc_coro.close() # GEN_CLOSED
- 코루틴에서 값 반환하기
from collections import namedtuple Result = namedtuple("Result", "count average") def averager(): total = 0.0 count = 0 average = None while True: term = yield if term is None: break total += term count += 1 average = total/count return Result(count, average) coro_avg = averager() next(coro_avg) coro_avg.send(10) coro_avg.send(30) coro_avg.send(6.5) try: coro_avg.send(None) except StopIteration as exc: result = exc.value # Result(count=3, average=15.5)
yield from
사용하기- 반복형 객체를 연결할 수 있다.
def gen(): # for c in "AB": # yield c yield from "AB"
- 코루틴 위임(coroutine delegation)
- 용어 정리
- 대표 제너레이터(delegating generator):
yield from <interator>
표현식을 담고 있는 제너레이터 함수 - 하위 제너레이터(subgenerator):
yield from
표현식 중<interator>
에서 가져오는 제너레이터 - 호출자(caller): 대표 제너레이터를 호출하는 코드
- 대표 제너레이터(delegating generator):
- 예제
- 머리가 터질 수 있음 ```python from collections import namedtuple from pprint import pprint
Result = namedtuple(“Result”, “count average”)
# subgenerator def averager(): total = 0.0 count = 0 average = None while True: term = yield if term is None: break total += term count += 1 average = total / count return Result(count, average)
# delegating generator def grouper(results, key): while True: results[key] = yield from averager()
# caller def main(data): results = {} for key, values in data.items(): group = grouper(results, key) next(group) for value in values: group.send(value) group.send(None) # print(results) report(results)
def report(results): pprint(results) # for key, result in sorted(results.items()): # group, unit = key.split(“;”) # print(f”{group:2} {result.count:5} {result.average:10.2f} {unit}”)
data = { “girls;kg”: [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5], “girls;m”: [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43], “boys;kg”: [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3], “boys;m”: [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46], }
main(data)
{‘boys;kg’: Result(count=9, average=40.422222222222224), ‘boys;m’: Result(count=9, average=1.3888888888888888), ‘girls;kg’: Result(count=10, average=42.040000000000006), ‘girls;m’: Result(count=10, average=1.4279999999999997)} ```
yield from
구현 안에next()
가 포함되어 있기 때문에 외부에서 먼저 priming 하면 안 된다.
- 용어 정리
- 반복형 객체를 연결할 수 있다.