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을 보내서 숫자 누적을 끝냄
이때 accumulate는 None을 받으면 코루틴이 완전히 끝나지만 sum_coroutine에서 무한 루프로 반복하고 있으므로 print로 total을 출력한 뒤 다시 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(값)처럼 사용할 수도 있습니다(파이썬 3.6 이하). 이렇게 raise로 StopIteration 예외를 직접 발생시키고 값을 지정하면 yield from으로 값을 가져올 수 있습니다(단, 파이썬 3.7부터는 제너레이터 안에서 raise로 StopIteration 예외를 직접 발생시키면 RuntimeError로 바뀌므로 이 방법은 사용할 수 없습니다. 파이썬 3.7부터는 그냥 return 값을 사용해주세요).
- raise StopIteration(값)
coroutine_stopiteration.py
def accumulate(): total = 0 while True: x = (yield) # 코루틴 바깥에서 값을 받아옴 if x is None: # 받아온 값이 None이면 raise StopIteration(total) # StopIteration에 반환할 값을 지정(파이썬 3.6 이하) 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 from은 accumulate의 total을 가져옵니다.
지금까지 코루틴에 대해 배웠습니다. 코루틴은 함수가 종료되지 않은 상태에서 값을 주고 받을 수 있는 함수이며 이 과정에서 현재 코드의 실행을 대기하고 상대방의 코드를 실행한다는 점이 중요합니다. 보통 코루틴은 시간이 오래 걸리는 작업을 분할하여 처리할 때 사용하는데 주로 파일 처리, 네트워크 처리 등에 활용합니다.
코루틴이 당장 이해가 되지 않는다고 해서 걱정할 필요가 없습니다. 현직 프로그래머들도 다소 어려워하는 부분입니다. 나중에 코루틴이 필요할 때 다시 돌아와서 학습하세요.
이번 예제에서는 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