Python 3.3: yield from을 사용한 서브 제너레이터 위임

Python 3.3 버전부터 도입된 yield from 구문은 제너레이터가 다른 제너레이터의 동작을 위임할 수 있도록 하는 강력한 기능을 제공합니다. 이 기능은 복잡한 제너레이터 로직을 더 작고 관리하기 쉬운 단위로 분해하고, 제너레이터 간의 상호작용을 간소화하는 데 큰 도움을 줍니다. 이전에는 제너레이터의 출력을 다른 제너레이터로 전달하기 위해 명시적인 반복문이 필요했지만, yield from은 이러한 과정을 훨씬 효율적으로 만들어 줍니다.

yield from의 작동 방식

yield from 구문은 다음과 같은 동작을 수행합니다:

  • 은 이터러블(iterable)을 평가하여 이터레이터(iterator)를 생성합니다.
  • 이 이터레이터가 소진될 때까지, yield from은 위임 제너레이터(delegating generator)가 값을 생성하는 것처럼 값을 직접 호출자에게 전달합니다.
  • 호출자로부터 위임 제너레이터로 send() 메소드를 통해 값이 전달되면, 해당 값은 직접 서브 제너레이터(subgenerator)에게 전달됩니다.
  • 서브 제너레이터에서 return value가 발생하면, 이는 StopIteration(value) 예외를 발생시키며, 이 valueyield from 표현식의 결과가 됩니다.

공식 시맨틱

RESULT = yield from EXPR 구문은 내부적으로 다음과 유사하게 처리됩니다:


_i = iter(EXPR)
try:
    _y = next(_i)
except StopIteration as _e:
    _r = _e.value
else:
    while True:
        try:
            _s = yield _y
        except GeneratorExit as _e:
            try:
                _m = _i.close
            except AttributeError:
                pass
            else:
                _m()
            raise _e
        except BaseException as _e:
            _x = sys.exc_info()
            try:
                _m = _i.throw
            except AttributeError:
                raise _e
            else:
                try:
                    _y = _m(*_x)
                except StopIteration as _e:
                    _r = _e.value
                    break
        else:
            try:
                if _s is None:
                    _y = next(_i)
                else:
                    _y = _i.send(_s)
            except StopIteration as _e:
                _r = _e.value
                break
RESULT = _r

또한, 제너레이터 내에서의 return valueraise StopIteration(value)와 동일하게 처리됩니다. StopIteration 예외는 value 속성을 포함하여 반환값을 저장할 수 있도록 확장되었습니다.

yield from의 이점

  • 코드 재사용성 향상: 복잡한 제너레이터 로직을 별도의 제너레이터 함수로 분리하여 재사용하기 용이합니다.
  • 가독성 증진: 제너레이터 간의 위임 관계가 명확해져 코드의 이해도를 높입니다.
  • 효율성 개선: 명시적인 반복문보다 더 효율적인 방식으로 서브 제너레이터와 통신할 수 있습니다.
  • 协程(Coroutine) 기능 강화: 제너레이터를 코루틴으로 사용할 때, 값 반환 및 예외 처리를 간결하게 할 수 있어 경량 스레드 구현 등에 유용합니다.

예시

다음은 yield from을 사용하여 간단한 순차적 데이터 처리를 위임하는 예시입니다.


def sub_generator():
    print("Sub: Starting")
    received = yield from producer()
    print(f"Sub: Producer finished with value: {received}")
    yield "Sub: Done"
    return "Sub: Final Result"

def producer():
    print("Producer: Yielding 1")
    yield 1
    print("Producer: Yielding 2")
    yield 2
    print("Producer: Returning value 100")
    return 100

def main_generator():
    print("Main: Calling sub_generator")
    result_from_sub = yield from sub_generator()
    print(f"Main: Received from sub: {result_from_sub}")

# 실행
gen = main_generator()
try:
    while True:
        print(f"Main: Sending None, received: {next(gen)}")
except StopIteration as e:
    print(f"Main: Generator finished with return value: {e.value}")

위 예시를 실행하면 다음과 같은 출력을 볼 수 있습니다:


Main: Calling sub_generator
Sub: Starting
Producer: Yielding 1
Main: Sending None, received: 1
Producer: Yielding 2
Main: Sending None, received: 2
Sub: Producer finished with value: 100
Main: Sending None, received: Sub: Done
Main: Generator finished with return value: Sub: Final Result

이 예시는 main_generatorsub_generator에게 제어를 위임하고, sub_generator는 다시 producer에게 위임하는 과정을 보여줍니다. producer가 값을 반환하면, 이 값은 StopIteration을 통해 sub_generator로 전달되고, 다시 yield from을 통해 main_generator에게 전달됩니다.

태그: python 제너레이터 yield from 코루틴 PEP 380

7월 28일 18:30에 게시됨