Effective Python Item 33. yield from을 사용해 여러 제네레이터를 합성하라.

    들어가기 전


    요약

    • yield from을 사용하면 여러 제네레이터를 합성해서 하나의 제네레이터를 만들 수 있음. 
    • 직접 내포된 제네레이터를 이터레이션하면서 각 제네레이터의 출력을 내보내는 것보다 yield from을 사용하는 것이 성능 면에서 더 좋음. 

     


    yield from

    def get_generator():
        k = list(range(10))
        for v in k:
            yield v
    
    
    def generator():
        for v in ["a", "b", "c"]:
            yield v
        yield from [1, 2, 3]
        yield from (4, 5, 6)
        yield from range(7, 11)
        yield from get_generator()
    
    gt = generator()
    while (n := next(gt)) is not None:
        print(n)
    • yield from 키워드는 다음처럼 사용할 수 있다.

    • yield from 키워드는 제네레이터 내에서 다른 제네레이터(자식 제네레이터)에 값의 생성을 위임하고 싶을 때 사용한다.
      • 제네레이터는 내부적으로 gi_yieldfrom이라는 attribute를 가지고 있고, gi_yieldfrom이 None이 아닐 때 gi_yieldfrom에 명시된 제네레이터에게 값의 생성을 요청한다. 
    • next(gt) → next(gt.gi_yield_from)이 호출되는 식으로 Deligation 된다. 
      • gi_yield_from 제네레이터에서 StopIteration 에러가 발생하면, gi_yield_from은 None이 되고, next(gt) 호출 시 다음 코드 블록으로 넘어가게 된다. 
    • gi_yieldfrom에 제네레이터가 설정되어있는 동안, next(gt)를 호출했을 때 제어권은 모두 gi_yieldfrom이 가지고 있다. gi_yieldfrom에서 StopIteration이 발생해서 gi_yieldfrom이 None이 되는 순간, 그 때 부모 제네레이터가 제어권을 가진다. 

     


    Item 33. yield from을 사용해 여러 제네레이터를 합성하라.

    하나의 제네레이터 함수에 여러 제네레이터를 포함하는 경우가 있다면 yield from을 사용할 것을 권장한다. 그 이유는 아래 코드를 보면 알 수 있다.

    move(period, speed):
        for _ in range(period):
            yield speed
    
    def pause(delay):
        for _ in range(delay):
            yield 0
    
    def animate():
        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
    
    for delta in animate():
        print(f'Delta: {delta:.1f}')

    여기서 animate() 함수의 단점을 살펴보자.

    • animate() 함수에서는 3개의 제네레이터가 존재하는데 각각 이터레이팅을 해야한다. 
    • 단점
      • 여러 번 for 문을 사용했음. 
      • 변수 delta도 여러 번 선언되었다. 

    단점 중 두번째 부분이 가장 치명적이다. 변수 delta가 여러 번 선언되었고, 불필요하게 delta가 무엇인지에 대해서 인지해야한다. 서로 다른 변수가 선언되어도 문제다. 예를 들어 delta, diff, multifly 같은 변수가 각각 선언된 경우, 각 변수의 이름을 보고 컨텍스트를 계속 파악해야한다. 결론적으로 제네레이터 안에서 여러 제네레이터를 사용하게 되면 문법적으로 잡음이 존재해서 가독성이 떨어진다. 

    def move(period, speed):
        for _ in range(period):
            yield speed
    
    
    def pause(delay):
        for _ in range(delay):
            yield 0
    
    
    def animate():
        yield from move(4, 5.0)
        yield from pause(3)
        yield from move(2, 3.0)
    
    
    for delta in animate():
        print(f'Delta: {delta:.1f}')

    위 문제의 개선을 위해서 제네레이터 내에서 여러 제네레이터가 내포된다면, yield from을 이용해서 단점을 보완할 수 있다. 

    • 여러 번 for 문을 사용했음.  → 사용하지 않도록 개선
    • 변수 delta도 여러 번 선언되었다.  → 선언하지 않도록 개선

     

     

    댓글

    Designed by JB FACTORY