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