#!/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을 사용하는 것이 성능 면에서 더 좋다.