yield from – 다른 제너레이터에게 작업을 위임하기

제너레이터 내부에서 yield 가 쓰일 때, .send() 통해서 내부로 전달된 값으로 평가될 수 있다고 하였다. 만약, 해당 제너레이터/코루틴이 매 입력을 직접 처리하지 않고 다른 제너레이터에게 일부 작업을 위임할 필요가 있다면 yield from 이라는 새로운 문법을 사용할 수 있다. 이 문법을 사용할 경우 다음과 같이 처리된다.

  1. yield from sub_generator()의 형태로 쓰인다.
  2. 부모 제너레이터의 send(x) 가 호출되면 이 값은 자식 제너레이터에게 전달된다.
  3. 자식 제너레이터가 yield 한 값은 다시 부모 제너레이터가 외부로 전달해준다. 하지만 실행 흐름은 더 이상 진척되지 않는다.
  4. 자식 제너레이터가 return 하는 경우 최종적으로 부모 제너레이터의 yield 가 평가되고 부모 제너레이터가 흐름을 재개한다.

PEP 문서에서 다음의 예제를 소개하고 있다. 주어진 값의 누계를 계산하는 제너레이터가 있다. 그리고 이 제너레이터에게 누계를 위임하는 코루틴이 있다. 이를 사용하여 일련의 수열의 누적합을 계산해보자.

def accumulate():
  total = 0
  while True:
    next = yield total
    if next is None:
      return total
    total += next

def gather(ls):
  while True:
    subtotal = yield from accumulate()
    ls.append(subtotal)

특이할만한 점은 서브로 쓰이는 제너레이터는 따로 next()를 사용해서 yield 지점까지 실행시켜둘 필요가 없다는 것이다. 대신에 부모제너레이터의 경우에는 시작시키는 과정이 필요하다.

tallies = []
acc = gather(tallies)
next(acc)

for i in range(4):
  acc.send(i)

## acc로 0,1,2,3 을 보내지만, tallies에는 아직 아무런 변화가 생기지 않는다.
## 아래 코드를 통해서 서브 제너레이터의 실행을 종료시킨다.
acc.send(None)

## 서브 제너레이터가 종료되면 acc의 나머지 코드가 실행되고 tallies가 업데이트된다.
print(tallies)
# [6]

## acc는 새로운 서브제너레이터를 준비중이다.
for i in range(4, 10):
  acc.send(i)
acc.send(None) ## 새 누계값 39가 추가된다.
print(tallies)
# [6, 39]

실질적으로 이런 식으로 코루틴의 위임을 응용할만한 케이스는 아직 딱히 떠오르지 않는다. 대신에 asyncio 에서 쓰이는 await 가 실제로는 코루틴 내에서 다른 코루틴에게 작업 처리를 위임하는 것이며, 실제로 yield from의 동의어가 await 이다.