Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 342 – 향상된 제너레이터를 통한 코루틴

Author:
Guido van Rossum, Phillip J. Eby
Status:
Final
Type:
Standards Track
Created:
10-May-2005
Python-Version:
2.5
Post-History:


Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

소개

이 PEP는 제너레이터를 간단한 코루틴으로 사용할 수 있도록 제너레이터의 API와 문법에 대한 몇 가지 개선 사항을 제안합니다. 이 PEP는 기본적으로 다음 두 PEP의 아이디어를 결합한 것이며, 이 PEP가 채택되면 두 PEP는 불필요한 것으로 간주될 수 있습니다:

  • PEP 288, 제너레이터 속성과 예외. 현재 PEP는 그 후반부인 제너레이터 예외를 다룹니다(실제로 throw() 메서드 이름은 PEP 288에서 가져온 것입니다). 그러나 PEP 342는 제너레이터 속성을 PEP 288의 이전 개정판에 있던 개념인 yield 표현식으로 대체합니다.
  • PEP 325, 제너레이터를 위한 자원 해제 지원. PEP 342는 PEP 325 명세에서 미결로 남아 있던 몇 가지 사항을 정리하여 실제 구현에 적합하도록 만듭니다.

동기

코루틴은 시뮬레이션, 게임, 비동기 I/O, 그리고 그 밖의 이벤트 기반 프로그래밍이나 협력적 멀티태스킹 같은 많은 알고리즘을 표현하는 자연스러운 방법입니다. Python의 제너레이터 함수는 거의 코루틴이지만 – 완전히 그렇지는 않습니다 – 실행을 일시 중지하여 값을 생성할 수는 있지만, 실행이 재개될 때 값이나 예외를 전달받는 방법은 제공하지 않기 때문입니다. 또한 제너레이터 함수는 try/finally 블록의 try 부분 안에서 실행을 일시 중지하는 것을 허용하지 않으므로, 중단된 코루틴이 스스로 뒷정리를 하기 어렵습니다.

또한 제너레이터는 다른 함수가 실행되는 동안에는 제어권을 양보할 수 없습니다. 단, 그 함수들 자체가 제너레이터로 표현되어 있고, 바깥쪽 제너레이터가 안쪽 제너레이터가 yield한 값에 응답하여 yield하도록 작성된 경우는 예외입니다. 이 때문에 비동기 통신처럼 비교적 단순한 사용 사례조차 구현이 복잡해집니다. 어떤 함수를 호출하든 제너레이터가 블록되거나(즉, 제어권을 양보할 수 없게 되거나), 아니면 필요한 모든 함수 호출 주위에 상투적인 루프 코드를 잔뜩 추가해야 하기 때문입니다.

그러나 제너레이터가 일시 중단된 지점에서 제너레이터 안으로 값이나 예외를 전달할 수 있다면, 간단한 코루틴 스케줄러나 트램펄린 함수를 통해 코루틴들이 블로킹 없이 서로를 호출할 수 있게 됩니다. 이는 비동기 애플리케이션에 엄청난 이점입니다. 그러면 이러한 애플리케이션은 데이터가 전송되거나 사용 가능해질 때까지 I/O 스케줄러에 제어권을 양보함으로써 논블로킹 소켓 I/O를 수행하는 코루틴을 작성할 수 있습니다. 한편, I/O를 수행하는 코드는 다음과 같이 간단하게 작성하면 됩니다:

data = (yield nonblocking_read(my_socket, nbytes))

이렇게 하면 nonblocking_read() 코루틴이 값을 생성할 때까지 실행이 일시 중지됩니다.

다시 말해, 언어와 제너레이터-이터레이터 타입의 구현을 비교적 사소하게 몇 가지 개선하면, Python은 애플리케이션 전체를 일련의 콜백으로 작성할 필요 없이, 그리고 수백, 심지어 수천 개의 협력적 멀티태스킹 의사 스레드가 필요한 프로그램에서 자원을 많이 소모하는 스레드를 사용할 필요 없이 비동기 연산 수행을 지원할 수 있게 됩니다. 따라서 이러한 개선은 CPython 코어나 그 API를 크게 수정하지 않고도 표준 파이썬에 Stackless Python 포크의 많은 이점을 제공할 것입니다. 또한 이러한 개선 사항은 이미 제너레이터를 지원하는 모든 Python 구현(예: Jython)에서 쉽게 구현할 수 있을 것입니다.

명세 요약

제너레이터-이터레이터 타입에 몇 가지 간단한 메서드를 추가하고 문법을 두 가지 조금 조정하면, Python 개발자는 제너레이터 함수를 사용하여 코루틴과 그 밖의 협력적 멀티태스킹 형태를 구현할 수 있게 됩니다. 이러한 메서드와 조정 사항은 다음과 같습니다:

  1. yield를 문(statement)이 아닌 표현식으로 재정의합니다. 현재의 yield 문은 값이 버려지는 yield 표현식이 됩니다. 제너레이터가 일반적인 next() 호출로 재개될 때마다 yield 표현식의 값은 None입니다.
  2. 제너레이터-이터레이터에 새로운 send() 메서드를 추가합니다. 이 메서드는 제너레이터를 재개하고, 현재 yield 표현식의 결과가 될 값을 보냅니다. send() 메서드는 제너레이터가 yield한 다음 값을 반환하며, 제너레이터가 다른 값을 yield하지 않고 종료되면 StopIteration을 발생시킵니다.
  3. 제너레이터-이터레이터에 새로운 throw() 메서드를 추가합니다. 이 메서드는 제너레이터가 일시 중지된 지점에서 예외를 발생시키고, 제너레이터가 yield한 다음 값을 반환하며, 제너레이터가 다른 값을 yield하지 않고 종료되면 StopIteration을 발생시킵니다. (제너레이터가 전달된 예외를 잡지 않거나 다른 예외를 발생시키면, 그 예외는 호출자에게 전파됩니다.)
  4. 제너레이터-이터레이터에 close() 메서드를 추가합니다. 이 메서드는 제너레이터가 일시 중지된 지점에서 GeneratorExit를 발생시킵니다. 그 후 제너레이터가 (정상적으로 종료되거나 이미 닫혀 있어서) StopIteration을 발생시키거나 (예외를 잡지 않아서) GeneratorExit를 발생시키면, close()는 호출자에게 반환합니다. 제너레이터가 값을 yield하면 RuntimeError가 발생합니다. 제너레이터가 그 밖의 다른 예외를 발생시키면, 그 예외는 호출자에게 전파됩니다. 제너레이터가 예외나 정상 종료로 인해 이미 종료된 경우 close()는 아무것도 하지 않습니다.
  5. 제너레이터 이터레이터가 가비지 컬렉션될 때 close()가 반드시 호출되도록 하는 지원을 추가합니다.
  6. try/finally 블록 안에서 yield를 사용할 수 있도록 허용합니다. 이제 가비지 컬렉션이나 명시적인 close() 호출로 finally 절이 실행될 수 있기 때문입니다.

이 모든 변경 사항을 현재 Python CVS HEAD에 대해 구현한 프로토타입 패치가 SourceForge 패치 #1223381 (https://bugs.python.org/issue1223381)로 제공됩니다.

명세: 제너레이터에 값 보내기

새 제너레이터 메서드: send(value)

제너레이터-이터레이터를 위한 새 메서드 send()를 제안합니다. 이 메서드는 정확히 하나의 인자를 받으며, 그 인자는 제너레이터에 보내질 값입니다. send(None)을 호출하는 것은 제너레이터의 next() 메서드를 호출하는 것과 정확히 동일합니다. 다른 값으로 send()를 호출하는 것도 같지만, 제너레이터의 현재 yield 표현식이 만들어 내는 값이 달라진다는 점만 다릅니다.

제너레이터-이터레이터는 제너레이터 함수 본문의 맨 처음부터 실행을 시작하므로, 제너레이터가 막 생성되었을 때에는 값을 받을 yield 표현식이 없습니다. 따라서 제너레이터 이터레이터가 막 시작되었을 때 None이 아닌 인자로 send()를 호출하는 것은 금지되며, 이런 일이 발생하면 (아마도 일종의 논리 오류 때문일 것이므로) TypeError가 발생합니다. 그러므로 코루틴과 통신하려면 먼저 next() 또는 send(None)을 호출하여 첫 번째 yield 표현식까지 실행을 진행시켜야 합니다.

next() 메서드와 마찬가지로, send() 메서드는 제너레이터-이터레이터가 yield한 다음 값을 반환하거나, 제너레이터가 정상적으로 종료하거나 이미 종료된 경우 StopIteration을 발생시킵니다. 제너레이터가 잡히지 않은 예외를 발생시키면, 그 예외는 send()의 호출자에게 전파됩니다.

새 문법: yield 표현식

yield 문을 대입의 우변에서 사용할 수 있게 됩니다. 이 경우 이를 yield 표현식이라고 부릅니다. send()가 None이 아닌 인자로 호출되지 않았다면 이 yield 표현식의 값은 None입니다. 아래를 참조하십시오.

yield 표현식은 대입 우변의 최상위 표현식으로 나타나는 경우를 제외하고는 항상 괄호로 감싸야 합니다. 따라서

x = yield 42
x = yield
x = 12 + (yield 42)
x = 12 + (yield)
foo(yield 42)
foo(yield)

위 예들은 모두 적법하지만,

x = 12 + yield 42
x = 12 + yield
foo(yield 42, 12)
foo(yield, 12)

위 예들은 모두 적법하지 않습니다. (일부 경계 사례는 현재 yield 12, 42가 적법하다는 점에서 비롯되었습니다.)

이제 표현식이 없는 yield 문이나 yield 표현식도 적법하다는 점에 유의하십시오. 이는 타당합니다. next() 호출에서 정보의 흐름이 역전되면, 명시적인 값을 전달하지 않고도 yield할 수 있어야 하기 때문입니다(yield는 물론 yield None과 동일합니다).

send(value)가 호출되면, 그 호출이 재개하는 yield 표현식은 전달된 값을 반환합니다. next()가 호출되면, 재개된 yield 표현식은 None을 반환합니다. yield 표현식이 yield 문이라면, 문으로 사용된 함수 호출이 반환한 값을 무시하는 것과 마찬가지로 이 반환 값은 무시됩니다.

사실상 yield 표현식은 뒤집힌 함수 호출과 같습니다. yield의 인자는 실제로는 현재 실행 중인 함수에서 반환(yield)되며, yield의 반환값은 send()를 통해 전달된 인자입니다.

참고: yield에 대한 구문 확장으로 yield의 사용법이 Ruby에서의 사용법과 매우 비슷해집니다. 이는 의도된 것입니다. 다만 Python에서는 블록이 return EXPR이 아니라 send(EXPR)를 사용해 제너레이터에 값을 전달하며, 제너레이터와 블록 사이에서 제어가 전달되는 기반 메커니즘은 완전히 다르다는 점에 유의하십시오. Python의 블록은 썽크(thunk)로 컴파일되지 않습니다. 대신 yield가 제너레이터 프레임의 실행을 일시 중단합니다. 일부 경계 사례는 다르게 동작합니다. Python에서는 블록을 나중에 사용하기 위해 저장할 수 없으며, 블록이 있는지 없는지 검사할 수도 없습니다. (XXX - 블록에 관한 이 내용은 이제 맥락에 맞지 않는 것 같습니다. 아마 Guido가 명확하게 수정할 수 있을 것입니다.)

명세: 예외와 정리

제너레이터 함수를 호출하여 생성된 이터레이터를 제너레이터 객체라고 합시다. 아래에서 g는 항상 제너레이터 객체를 가리킵니다.

새로운 문법: try-finally 안에서 yield 허용

제너레이터 함수의 문법이 확장되어 try-finally 문 안에서 yield 문을 사용할 수 있습니다.

새 제너레이터 메서드: throw(type, value=None, traceback=None)

g.throw(type, value, traceback)는 제너레이터 g가 현재 일시 중단된 지점(즉, yield 문, 또는 아직 next()가 호출되지 않았다면 함수 본문의 시작 부분)에서 지정된 예외가 던져지게 합니다. 제너레이터가 예외를 잡고 다른 값을 yield하면, 그 값이 g.throw()의 반환값이 됩니다. 제너레이터가 예외를 잡지 않으면, throw()는 전달받은 것과 같은 예외를 발생시키는 것처럼 보입니다(예외가 그대로 빠져나갑니다). 제너레이터가 다른 예외를 발생시키면(제너레이터가 반환할 때 생성되는 StopIteration도 포함됩니다) 그 예외는 throw() 호출에서 발생합니다. 요약하면, throw()는 일시 중단 지점에서 예외를 발생시킨다는 점을 제외하면 next()나 send()처럼 동작합니다. 제너레이터가 이미 닫힌 상태라면, throw()는 제너레이터의 코드를 전혀 실행하지 않고 전달받은 예외를 그대로 발생시킵니다.

예외를 발생시키는 효과는 다음 문이:

raise type, value, traceback

일시 중단 지점에서 실행된 것과 정확히 같습니다. type 인자는 None이어서는 안 되며, type과 value는 서로 호환되어야 합니다. value가 type의 인스턴스가 아니면, raise 문이 예외 인스턴스를 생성할 때 사용하는 것과 같은 규칙에 따라 value를 사용하여 새 예외 인스턴스가 생성됩니다. traceback이 주어진다면 유효한 Python 트레이스백 객체여야 하며, 그렇지 않으면 TypeError가 발생합니다.

참고: throw() 메서드의 이름은 여러 가지 이유로 선택되었습니다. Raise는 키워드이므로 메서드 이름으로 사용할 수 없습니다. (현재 실행 지점에서 즉시 예외를 발생시키는) raise와 달리, throw()는 먼저 제너레이터를 재개한 다음에야 예외를 발생시킵니다. throw라는 단어는 예외를 다른 위치에 놓는다는 의미를 암시하며, 다른 언어에서도 이미 예외와 연관되어 쓰이고 있습니다.

대안이 되는 메서드 이름으로 resolve(), signal(), genraise(), raiseinto(), flush()가 검토되었습니다. 이 중 어느 것도 throw()만큼 잘 어울려 보이지 않습니다.

새 표준 예외: GeneratorExit

Exception을 상속하는 새 표준 예외 GeneratorExit이 정의됩니다. 제너레이터는 이 예외를 다시 발생시키거나(또는 그냥 잡지 않거나) StopIteration을 발생시켜서 처리해야 합니다.

새 제너레이터 메서드: close()

g.close()는 다음 의사 코드로 정의됩니다:

def close(self):
    try:
        self.throw(GeneratorExit)
    except (GeneratorExit, StopIteration):
        pass
    else:
        raise RuntimeError("generator ignored GeneratorExit")
    # Other exceptions are not caught

새 제너레이터 메서드: __del__()

g.__del__()는 g.close()의 래퍼입니다. 이 메서드는 제너레이터 객체가 가비지 컬렉션될 때 호출됩니다(CPython에서는 참조 횟수가 0이 될 때입니다). close()가 예외를 발생시키면 해당 예외의 트레이스백이 sys.stderr에 출력되고 이후로는 무시됩니다. 이 예외는 가비지 컬렉션을 유발한 곳으로 다시 전파되지 않습니다. 이는 클래스 인스턴스의 __del__() 메서드에서 예외를 처리하는 방식과 일치합니다.

제너레이터 객체가 순환에 참여하면 g.__del__()가 호출되지 않을 수 있습니다. 이는 CPython의 현재 가비지 컬렉터의 동작입니다. 이러한 제약이 있는 이유는 GC 코드가 순환을 수집하려면 임의의 지점에서 순환을 끊어야 하며, 그 이후로는 순환을 이루던 객체가 유효하지 않은 상태일 수 있으므로 어떤 Python 코드도 그 객체를 볼 수 없어야 하기 때문입니다. 순환에 매달려 있는 객체는 이 제약을 받지 않습니다.

실제로 제너레이터 객체가 순환에 참여하는 경우는 드물다는 점에 유의하십시오. 그러나 제너레이터 객체를 전역 변수에 저장하면 제너레이터 프레임의 f_globals 포인터를 통해 순환이 만들어집니다. 순환을 만드는 또 다른 방법은 제너레이터에 인자로 전달되는 자료 구조에 제너레이터 객체에 대한 참조를 저장하는 것입니다(예를 들어, 어떤 객체가 제너레이터인 메서드를 가지고 있고, 그 메서드가 만든 실행 중인 이터레이터에 대한 참조를 유지하는 경우). 일반적인 제너레이터 사용 패턴을 고려하면 두 경우 모두 일어날 가능성은 그리 높지 않습니다.

또한 이 PEP의 CPython 구현에서는 제너레이터가 사용하는 프레임 객체를 오류나 정상 종료로 실행이 끝날 때마다 해제해야 합니다. 이렇게 하면 재개할 수 없는 제너레이터가 수집할 수 없는 참조 순환의 일부로 남지 않게 됩니다. 이를 통해 다른 코드에서 주어진 제너레이터가 제대로 마무리되도록 try/finally 또는 with 블록(PEP 343에 따름) 안에서 close()를 사용할 수도 있습니다.

선택적 확장

확장된 continue 문

이 PEP의 이전 초안에서는 for 루프에서 사용할 새로운 continue EXPR 문법(PEP 340에서 이어받은 것)을 제안했으며, 이 문법은 EXPR의 값을 루프를 돌고 있는 이터레이터에 전달하는 것이었습니다. 이 PEP의 범위가 다른 종류의 이터레이터가 아닌 제너레이터-이터레이터에 값을 전달하는 것에만 집중하도록 좁혀졌기 때문에 이 기능은 당분간 철회되었습니다. 또한 Python-Dev 리스트의 일부 참여자들은 이 특정 기능을 위해 새로운 문법을 추가하는 것은 아무리 좋게 보아도 시기상조라고 생각했습니다.

미해결 문제

python-dev에서 논의하는 과정에서 몇 가지 미해결 문제가 드러났습니다. 여기에 그 문제들을 제가 선호하는 해결 방안 및 그 동기와 함께 나열합니다. 현재 작성된 PEP는 이 선호하는 해결 방안을 반영하고 있습니다.

  1. 제너레이터가 GeneratorExit 예외에 대한 응답으로 또 다른 값을 산출할 때 close()는 어떤 예외를 발생시켜야 합니까?

    저는 처음에 TypeError를 선택했습니다. 이 상황은 제너레이터 함수의 심각한 오동작을 나타내며, 코드를 변경해서 고쳐야 하기 때문입니다. 하지만 PEP 343의 with_template 데코레이터 클래스는 비슷한 위반에 RuntimeError를 사용합니다. 모두 같은 예외를 사용해야 한다고 볼 수도 있습니다. 저는 이 목적만을 위해 새로운 예외 클래스를 도입하고 싶지는 않습니다. 사람들이 잡기를 바라는 예외가 아니기 때문입니다. 저는 이 예외가 트레이스백으로 바뀌어 프로그래머가 그것을 보고 코드를 고치기를 바랍니다. 그래서 이제 저는 둘 다 RuntimeError를 발생시켜야 한다고 생각합니다. 이에 대한 몇 가지 선례가 있습니다. 핵심 Python 코드는 무한 재귀가 감지된 상황과 초기화되지 않은 객체에 대해(그리고 기타 다양한 조건에서) 이 예외를 발생시킵니다.

  2. Oren Tirosh는 소비자 인터페이스와의 호환성을 위해 send() 메서드의 이름을 feed()로 바꾸자고 제안했습니다(명세는 http://effbot.org/zone/consumer.htm 을 참고하십시오).

    그러나 소비자 인터페이스를 좀 더 자세히 살펴보면, feed()에 요구되는 의미론은 send()와 다른 것으로 보입니다. send()는 막 시작된 제너레이터에 대해서는 의미 있게 호출할 수 없기 때문입니다. 또한 현재 정의된 소비자 인터페이스에는 StopIteration에 대한 처리가 포함되어 있지 않습니다.

    따라서 제너레이터 함수를 감싸서 소비자 인터페이스를 따르도록 만드는 간단한 데코레이터를 만드는 편이 아마 더 유용할 것으로 보입니다. 예를 들어, 이 데코레이터는 최초의 next() 호출로 제너레이터를 예열하고, StopIteration을 가로채며, 어쩌면 제너레이터 함수를 다시 호출하는 방식으로 reset()까지 제공할 수도 있을 것입니다.

예시

  1. 제너레이터 함수가 처음 호출될 때 자동으로 첫 번째 yield 지점까지 진행하도록 만드는 간단한 consumer 데코레이터:
    def consumer(func):
        def wrapper(*args,**kw):
            gen = func(*args, **kw)
            gen.next()
            return gen
        wrapper.__name__ = func.__name__
        wrapper.__dict__ = func.__dict__
        wrapper.__doc__  = func.__doc__
        return wrapper
    
  2. consumer 데코레이터를 사용하여, 이미지를 받아 썸네일 페이지를 만들고 이를 다른 소비자에게 전달하는 역방향 제너레이터를 만드는 예제 이와 같은 함수들을 서로 연결하면, 각각 복잡한 내부 상태를 가질 수 있는 소비자들로 이루어진 효율적인 처리 파이프라인을 구성할 수 있습니다:
    @consumer
    def thumbnail_pager(pagesize, thumbsize, destination):
        while True:
            page = new_image(pagesize)
            rows, columns = pagesize / thumbsize
            pending = False
            try:
                for row in xrange(rows):
                    for column in xrange(columns):
                        thumb = create_thumbnail((yield), thumbsize)
                        page.write(
                            thumb, col*thumbsize.x, row*thumbsize.y )
                        pending = True
            except GeneratorExit:
                # close() was called, so flush any pending output
                if pending:
                    destination.send(page)
    
                # then close the downstream consumer, and exit
                destination.close()
                return
            else:
                # we finished a page full of thumbnails, so send it
                # downstream and keep on looping
                destination.send(page)
    
    @consumer
    def jpeg_writer(dirname):
        fileno = 1
        while True:
            filename = os.path.join(dirname,"page%04d.jpg" % fileno)
            write_jpeg((yield), filename)
            fileno += 1
    
    
    # Put them together to make a function that makes thumbnail
    # pages from a list of images and other parameters.
    #
    def write_thumbnails(pagesize, thumbsize, images, output_dir):
        pipeline = thumbnail_pager(
            pagesize, thumbsize, jpeg_writer(output_dir)
        )
    
        for image in images:
            pipeline.send(image)
    
        pipeline.close()
    
  3. 코루틴이 호출하려는 코루틴을 yield 함으로써 다른 코루틴을 호출할 수 있게 해 주는 간단한 코루틴 스케줄러, 즉 트램펄린 코루틴이 yield 한 값 중 제너레이터가 아닌 값은 모두, 그 값을 yield 한 코루틴을 호출한 코루틴에게 반환됩니다. 마찬가지로, 코루틴이 예외를 발생시키면 그 예외는 해당 코루틴의 호출자에게 전파됩니다. 사실상 이 예제는, 그렇지 않으면 블록될 루틴을 yield 표현식으로 호출하기만 한다면, Stackless Python에서 사용하는 것과 같은 간단한 태스클릿을 흉내 냅니다. 이것은 아주 간단한 예제일 뿐이며, 훨씬 더 정교한 스케줄러도 얼마든지 가능합니다. (예를 들어, 기존의 Python용 GTasklet 프레임워크(http://www.gnome.org/~gjc/gtasklet/gtasklets.html)와 peak.events 프레임워크(http://peak.telecommunity.com/)는 이미 비슷한 스케줄링 기능을 구현하고 있지만, 현재로서는 제너레이터에 값이나 예외를 전달할 수 없다는 점 때문에 어색한 우회 방법을 사용해야 합니다.)
    import collections
    
    class Trampoline:
        """Manage communications between coroutines"""
    
        running = False
    
        def __init__(self):
            self.queue = collections.deque()
    
        def add(self, coroutine):
            """Request that a coroutine be executed"""
            self.schedule(coroutine)
    
        def run(self):
            result = None
            self.running = True
            try:
                while self.running and self.queue:
                   func = self.queue.popleft()
                   result = func()
                return result
            finally:
                self.running = False
    
        def stop(self):
            self.running = False
    
        def schedule(self, coroutine, stack=(), val=None, *exc):
            def resume():
                value = val
                try:
                    if exc:
                        value = coroutine.throw(value,*exc)
                    else:
                        value = coroutine.send(value)
                except:
                    if stack:
                        # send the error back to the "caller"
                        self.schedule(
                            stack[0], stack[1], *sys.exc_info()
                        )
                    else:
                        # Nothing left in this pseudothread to
                        # handle it, let it propagate to the
                        # run loop
                        raise
    
                if isinstance(value, types.GeneratorType):
                    # Yielded to a specific coroutine, push the
                    # current one on the stack, and call the new
                    # one with no args
                    self.schedule(value, (coroutine,stack))
    
                elif stack:
                    # Yielded a result, pop the stack and send the
                    # value to the caller
                    self.schedule(stack[0], stack[1], value)
    
                # else: this pseudothread has ended
    
            self.queue.append(resume)
    
  4. 간단한 에코 서버와, 이를 트램펄린으로 실행하는 코드(nonblocking_read, nonblocking_write 및 기타 I/O 코루틴이 존재한다고 가정하며, 이 코루틴들은 예컨대 연결이 닫히면 ConnectionLost를 발생시킵니다):
    # coroutine function that echos data back on a connected
    # socket
    #
    def echo_handler(sock):
        while True:
            try:
                data = yield nonblocking_read(sock)
                yield nonblocking_write(sock, data)
            except ConnectionLost:
                pass  # exit normally if connection lost
    
    # coroutine function that listens for connections on a
    # socket, and then launches a service "handler" coroutine
    # to service the connection
    #
    def listen_on(trampoline, sock, handler):
        while True:
            # get the next incoming connection
            connected_socket = yield nonblocking_accept(sock)
    
            # start another coroutine to handle the connection
            trampoline.add( handler(connected_socket) )
    
    # Create a scheduler to manage all our coroutines
    t = Trampoline()
    
    # Create a coroutine instance to run the echo_handler on
    # incoming connections
    #
    server = listen_on(
        t, listening_socket("localhost","echo"), echo_handler
    )
    
    # Add the coroutine to the scheduler
    t.add(server)
    
    # loop forever, accepting connections and servicing them
    # "in parallel"
    #
    t.run()
    

참조 구현

이 PEP에서 설명한 모든 기능을 구현한 프로토타입 패치가 SourceForge 패치 #1223381(https://bugs.python.org/issue1223381)로 제공됩니다.

이 패치는 2005년 8월 1~2일에 CVS에 커밋되었습니다.

감사의 글

Raymond Hettinger(PEP 288)와 Samuele Pedroni(PEP 325)가 제너레이터에 값이나 예외를 전달한다는 아이디어와 제너레이터를 닫는 기능을 처음으로 공식 제안했습니다. Timothy Delaney가 이 PEP의 제목을 제안했고, Steven Bethard가 이전 버전의 편집을 도왔습니다. PEP 340의 감사의 말 절도 참고하십시오.

참고 문헌

미정.