41.4 하위 코루틴의 반환값 가져오기

제너레이터에서 yield from을 사용하면 값을 바깥으로 여러 번 전달한다고 했습니다('40.3 yield from으로 값을 여러 번 바깥으로 전달하기' 참조). 하지만 코루틴에서는 조금 다르게 사용합니다. yield from에 코루틴를 지정하면 해당 코루틴에서 return으로 반환한 값을 가져옵니다(yield from은 파이썬 3.3 이상부터 사용 가능)

  • 변수 = yield from 코루틴()

다음은 코루틴에서 숫자를 누적한 뒤 합계를 yield from으로 가져옵니다.

coroutine_yield_from.py

def accumulate():
    total = 0
    while True:
        x = (yield)         # 코루틴 바깥에서 값을 받아옴
        if x is None:       # 받아온 값이 None이면
            return total    # 합계 total을 반환
        total += x
 
def sum_coroutine():
    while True:
        total = yield from accumulate()    # accumulate의 반환값을 가져옴
        print(total)
 
co = sum_coroutine()
next(co)
 
for i in range(1, 11):    # 1부터 10까지 반복
    co.send(i)            # 코루틴 accumulate에 숫자를 보냄
co.send(None)             # 코루틴 accumulate에 None을 보내서 숫자 누적을 끝냄
 
for i in range(1, 101):   # 1부터 100까지 반복
    co.send(i)            # 코루틴 accumulate에 숫자를 보냄
co.send(None)             # 코루틴 accumulate에 None을 보내서 숫자 누적을 끝냄

실행 결과

55
5050

코루틴에 1부터 10까지 보내서 합계 55를 구하고, 다시 1부터 100까지 보내서 합계 5050을 구했습니다.

먼저 숫자를 받아서 누적할 코루틴을 만듭니다. x = (yield)와 같이 코루틴 바깥에서 값을 받아온 뒤 total에 계속 더합니다. 특히 이 코루틴은 while True:로 무한히 반복하지만 코루틴을 끝낼 방법이 필요합니다. 여기서는 코루틴 바깥에서 받아온 값이 None이면 return으로 total을 반환하고 코루틴을 끝냅니다.

def accumulate():
    total = 0
    while True:
        x = (yield)         # 코루틴 바깥에서 값을 받아옴
        if x is None:       # 받아온 값이 None이면
            return total    # 합계 total을 반환, 코루틴을 끝냄
        total += x

이제 합계를 출력할 코루틴을 만듭니다. 먼저 while True:로 무한히 반복합니다. 그리고 total = yield from accumulate()와 같이 yield from을 사용하여 코루틴 accumulate의 반환값을 가져옵니다.

def sum_coroutine():
    while True:
        total = yield from accumulate()    # accumulate의 반환값을 가져옴
        print(total)

코루틴에서 yield from을 사용하면 코루틴 바깥에서 send로 하위 코루틴까지 값을 보낼 수 있습니다. 따라서 co = sum_coroutine()으로 코루틴 객체를 만든 뒤 co.send로 값을 보내면 accumulate에서 값을 받습니다.

co = sum_coroutine()
next(co)
 
for i in range(1, 11):    # 1부터 10까지 반복
    co.send(i)            # 코루틴 accumulate에 숫자를 보냄

co.send로 숫자를 계속 보내다가 누적을 끝내고 싶으면 None을 보내면 됩니다.

co.send(None)             # 코루틴 accumulate에 None을 보내서 숫자 누적을 끝냄

이때 accumulateNone을 받으면 코루틴이 완전히 끝나지만 sum_coroutine에서 무한 루프로 반복하고 있으므로 printtotal을 출력한 뒤 다시 yield from accumulate()accumulate를 실행하게 됩니다.

def sum_coroutine():
    while True:
        total = yield from accumulate()    # accumulate가 끝나면 yield from으로 다시 실행
        print(total)

41.4.1  StopIteration 예외 발생시키기

코루틴도 제너레이터이므로 return을 사용하면 StopIteration이 발생합니다. 사실 코루틴에서 return raise StopIteration(값)과 동작이 같습니다. 따라서 raise로 예외를 직접 발생시키고 값을 지정하면 yield from으로 값을 가져올 수 있습니다.

  • raise StopIteration(값)

coroutine_stopiteration.py

def accumulate():
    total = 0
    while True:
        x = (yield)                       # 코루틴 바깥에서 값을 받아옴
        if x is None:                     # 받아온 값이 None이면
            raise StopIteration(total)    # StopIteration에 반환할 값을 지정
        total += x
 
def sum_coroutine():
    while True:
        total = yield from accumulate()    # accumulate의 반환값을 가져옴
        print(total)
 
co = sum_coroutine()
next(co)
 
for i in range(1, 11):    # 1부터 10까지 반복
    co.send(i)            # 코루틴 accumulate에 숫자를 보냄
co.send(None)             # 코루틴 accumulate에 None을 보내서 숫자 누적을 끝냄
 
for i in range(1, 101):   # 1부터 100까지 반복
    co.send(i)            # 코루틴 accumulate에 숫자를 보냄
co.send(None)             # 코루틴 accumulate에 None을 보내서 숫자 누적을 끝냄

실행 결과

55
5050

accumulate에서 return total 대신 raise StopIteration(total)을 사용했습니다. 이때도 yield fromaccumulatetotal을 가져옵니다.

지금까지 코루틴에 대해 배웠습니다. 코루틴은 함수가 종료되지 않은 상태에서 값을 주고 받을 수 있는 함수이며 이 과정에서 현재 코드의 실행을 대기하고 상대방의 코드를 실행한다는 점이 중요합니다. 보통 코루틴은 시간이 오래 걸리는 작업을 분할하여 처리할 때 사용하는데 주로 파일 처리, 네트워크 처리 등에 활용합니다.

코루틴이 당장 이해가 되지 않는다고 해서 걱정할 필요가 없습니다. 현직 프로그래머들도 다소 어려워하는 부분입니다. 나중에 코루틴이 필요할 때 다시 돌아와서 학습하세요.

참고 | 코루틴의 yield from으로 값을 발생시키기

이번 예제에서는 x = (yield)와 같이 코루틴 바깥에서 보낸 값만 받아왔습니다. 하지만 코루틴에서 yield에 값을 지정해서 바깥으로 전달했다면 yield from은 해당 값을 다시 바깥으로 전달합니다.

coroutine_yield_yield_from.py

def number_coroutine():
    x = None
    while True:
        x = (yield x)    # 코루틴 바깥에서 값을 받아오면서 바깥으로 값을 전달
        if x == 3:
            return x
 
def print_coroutine():
    while True:
        x = yield from number_coroutine()   # 하위 코루틴의 yield에 지정된 값을 다시 바깥으로 전달
        print('print_coroutine:', x)
 
co = print_coroutine()
next(co)
 
x = co.send(1)    # number_coroutine으로 1을 보냄
print(x)          # 1: number_coroutine의 yield에서 바깥으로 전달한 값
x = co.send(2)    # number_coroutine으로 2를 보냄
print(x)          # 2: number_coroutine의 yield에서 바깥으로 전달한 값
co.send(3)        # 3을 보내서 반환값을 출력하도록 만듦

실행 결과

1
2
print_coroutine: 3