#!/usr/bin/env python # coding: utf-8 # ## 33. yield from을 사용해 여러 제너레이터를 합성하라 # 화면상 이동 변위를 만들어낼 떄 사용할 두 가지 제너레이터를 정의한 코드다. # In[1]: def move(period, speed): for _ in range(period): yield speed # In[2]: def pause(delay): for _ in range(delay): yield 0 # 최종 애니메이션을 만들려면 move와 pause를 합성해서 변위 시퀀스를 하나만들어야 한다. # # 애니메이션의 각 단계마다 제너레이터를 호출해서 차례로 이터레이션하고 각 이터레이션에서 나오는 변위를 순서대로 내보내는 방식으로 다음과 같이 시퀀스를 만든다. # In[3]: def animation(): for delta in move(4, 5.0): yield delta for delta in pause(3): yield delta for delta in move(2, 3.0): yield delta # In[4]: def render(delta): print(f'Delta: {delta:.1f}') # In[5]: def run(func): for delta in func(): render(delta) # In[7]: run(animation) # 이 코드의 문제점은 animate가 너무 반복적이라는 것이다. # # for 문과 yield 식이 반복되면서 잡음이 늘고 가독성이 줄어든다. # # 이 문제의 해법은 yield from 식을 사용하는 것이다. # # 이는 고급 제너레이터 기능으로, 제어를 부모 제너레이터에게 전달하기 전에 내포된 제너레이터가 모든 값을 내보낸다. # In[9]: def animate_composed(): yield from move(4, 5.0) yield from pause(3) yield from move(2, 3.0) # In[10]: run(animate_composed) # 동일한 결과. # # yield from은 근본적으로 파이썬 인터프리터가 여러분 대신 for 루프를 내포시키고 yield 식을 처리하도록 만든다. # # 이로 인해 성능도 더 좋아진다. # In[11]: import timeit def child(): for i in range(1_000_000): yield i def slow(): for i in child(): yield i def fast(): yield from child() baseline = timeit.timeit( stmt='for _ in slow(): pass', globals=globals(), number=50) print(f'수동 내포: {baseline:.2f}s') # In[13]: comparison = timeit.timeit( stmt='for _ in fast(): pass', globals=globals(), number=50) print(f'합성 사용: {comparison:.2f}s') # ## 기억해야 할 내용 # - yield from 식을 사용하면 여러 내장 제너레이터를 모아서 제너레이터 하나로 합성할 수 있다. # - 직접 내포된 제너레이터를 이터레이션하면서 각 제너레이터의 출력을 내보내는 것보다 yield from을 사용하는 것이 성능 면에서 더 좋다.