파이썬 yield from – 다른 코루틴에게 작업을 위임하기

파이썬에선 함수 내부에 yield 키워드가 쓰였다면 이는 일반적인 함수가 아니라 제너레이터를 만드는 제너레이터 함수(혹은 코루틴 함수)가 된다. 제너레이터는 next() 함수를 통해서 생성하는 값을 꺼낼 수 있고, 동시에 .send() 메소드를 써서 그 내부로 값을 전달할 수 있다. 특히 이렇게 값을 주입해 줄 수 있는 제너레이터를 코루틴이라 한다고 했다.

코루틴 혹은 제너레이터의 내부에서 다른 코루틴이나 제너레이터의 결과값을 그대로 사용하는 경우가 있을 수 있다. 예를 들어 다음의 경우, 주어진 값으로부터 1씩 내렸다가 다시 0부터 n-1까지 값을 생성하는 제너레이터가 있다고 하자.

def foo(n):
  for i in range(n, 0, -1):
    yield i
  for i in range(n):
    yield i

list(foo(5))
# -> [5, 4, 3, 2, 1, 0, 1, 2, 3, 4]

이렇게 제너레이터 내부에서 다른 제너레이터의 결과를 사용하는 경우에 yield from 으로 코드를 간결하게 만들 수 있다.

def foo(n):
  yield from range(n, 0, -1)
  yield from range(n)

사실 yield from은 실용적인 맥락에서는 별로 쓰일일이 없다. Python 3.5에서 비동기 코루틴의 결과를 기다리는 await 문이 추가되기 전에 같은 기능을 위한 문법으로 추가된 것이기 때문이다.


yield from이 작동하는 방식은 간단하다. 제너레이터/코루틴 내부에서 다른 제너레이터/코루틴에 대해서 yield from subcoroutine()을 사용하는 것이다.

  1. 이 경우 해당 코루틴에 대해 next(co), co.send(v)를 호출하는 것은 곧장 subcoroutine()의 동작으로 연결되며, co 내부의 yield from 지점에서는 코드가 진행되지 않는다.
  2. 서브코루틴이 동작을 완료하여 StopIteration 예외를 일으키면 yield from 구문이 종료되고 다음 라인으로 실행 흐름이 이동한다.

yield from을 사용하여 작업을 위임하는 경우, next()send()가 동일하게 위임된다. 차이가 있다면 next()send(None)과 똑같이 동작하게 된다는 것이다.

다음은 해당 기능을 설명한 PEP380에 등장하는 예제이다. 누계를 받아서 리턴해주는 acc() 코루틴을 사용해서 여러 수열의 누적합을 만드는 예이다. 유용할지는 모르지만, 작업 위임이 어떤식으로 동작하는지는 볼 수 있는 예이다.

def acc():
    s = 0
    while True:
        n = yield s
        if n is None:
            return s
        s += n
        

@coroutine
def gather(xs):
    while True:
        subtotal = yield from acc()
        xs.append(subtotal)

ns = []
g = gather(ns)
g.sender(10) # -> 10
g.sender(20) # -> 30
g.sender(30) # -> 60
next(g)
# ns = [60]