PEP 828 – 비동기 제너레이터에서 ‘yield from’ 지원
- Author:
- Peter Bierma <peter at python.org>
- PEP-Delegate:
- Yury Selivanov <yury at vercel.com>
- Discussions-To:
- Discourse thread
- Status:
- Accepted
- Type:
- Standards Track
- Created:
- 07-Mar-2026
- Python-Version:
- 3.16
- Post-History:
- 07-Mar-2026, 09-Mar-2026
- Resolution:
- 03-Aug-2026
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 비동기 제너레이터 함수에서 yield from 키워드에 대한 지원을 도입합니다:
async def agenerator():
yield 1
yield 2
return 3
async def main():
result = yield from agenerator()
assert result == 3
용어
이 PEP에서는 yield를 포함하는 async def 함수를 비동기 제너레이터라고 부르며, 때로는 뒤에 “함수”를 붙입니다. 이것을 비동기 제너레이터가 반환하는 객체인 비동기 제너레이터 이터레이터와 혼동해서는 안 됩니다.
또한 이 PEP는 yield from 안에서 사용되는 동기 또는 비동기 제너레이터를 가리켜 “서브제너레이터”라는 용어를 사용합니다.
동기
구현 복잡성이 낮아졌습니다
과거에는 구현 복잡성에 대한 우려 때문에 비동기 제너레이터에 yield from이 추가되지 않았습니다. PEP 525를 인용하면 다음과 같습니다:
비동기 제너레이터에yield from지원을 구현하는 것은 이론적으로 가능하지만, 그렇게 하려면 제너레이터 구현을 대대적으로 재설계해야 합니다.
2026년 3월 현재, 이 제안의 저자는 CPython의 비동기 제너레이터 구현의 현재 상태를 고려할 때 이것이 사실이라고 생각하지 않습니다. 이 제안은 이 주장을 뒷받침하기 위해 참조 구현을 함께 제공하지만, 복잡성은 종종 주관적이라는 점을 인정합니다.
동기 제너레이터와의 대칭성
다른 제너레이터로 위임하는 것은 유용한 일이기 때문에 PEP 380에서 동기 제너레이터에 yield from이 추가되었습니다. 앞서 언급한 CPython 제너레이터 구현의 복잡성 때문에 PEP 525는 비동기 제너레이터에서 yield from 지원을 생략했지만, 이로 인해 언어에 공백이 생겼습니다.
사용자들도 이 공백을 인지하고 있었습니다. 비동기 제너레이터에서 yield from 또는 (서로 밀접하게 관련된) return 동작을 지원해 달라는 요청이 세 차례 별도로 있었습니다:
- https://discuss.python.org/t/8897
- https://discuss.python.org/t/47050
- https://discuss.python.org/t/66886
또한 Stack Overflow(스택 오버플로)에서도 사용자들이 이 설계 결정에 대해 의문을 제기했습니다.
서브제너레이터 위임은 비동기 제너레이터에 유용합니다
비동기 제너레이터에서 yield from이 지원되지 않는 문제에 대한 현재의 우회 방법은 각 항목을 수동으로 yield하는 for/async for 루프를 사용하는 것입니다. 이 방법에는 몇 가지 단점이 있습니다:
명세
컴파일러 변경 사항
컴파일러는 더 이상 비동기 제너레이터 내부의 return 키워드나 yield from 문에 대해 SyntaxError를 발생시키지 않습니다.
StopAsyncIteration의 변경 사항
StopAsyncIteration 예외에는 비동기 제너레이터에서 yield from 표현식의 결과로 사용될 새로운 value 속성이 추가됩니다.
이 속성은 StopAsyncIteration에 위치 인자를 전달하여 제공할 수 있습니다. 예를 들면 다음과 같습니다:
>>> exception = StopAsyncIteration(42)
>>> exception.value
42
인자를 지정하지 않으면 value는 None이 됩니다.
비동기 제너레이터 내부의 return 문
비동기 제너레이터 함수 본문에서 return expression 문은 raise StopAsyncIteration(expression) 문과 대략 동등합니다. 그러나 동기 제너레이터 내부에서 발생하는 암묵적 StopIteration 예외와 마찬가지로, 이 예외는 비동기 제너레이터 본문에서 잡을 수 없습니다.
비동기 제너레이터에서의 yield from 의미론
비동기 제너레이터에서 다음 문은
RESULT = yield from EXPR
대략 다음과 동등합니다:
aiterator = aiter(EXPR)
try:
item = await anext(aiterator)
except StopAsyncIteration as stop:
RESULT = stop.value
else:
while True:
try:
received = yield item
except GeneratorExit as gen_exit:
try:
aclose = aiterator.aclose
except AttributeError:
pass
else:
await aclose()
raise gen_exit
except BaseException as exception:
try:
athrow = aiterator.athrow
except AttributeError:
raise exception from None
else:
try:
item = await athrow(exception)
except StopAsyncIteration as stop:
RESULT = stop.value
break
else:
try:
if received is None:
item = await anext(aiterator)
else:
item = await aiterator.asend(received)
except StopAsyncIteration as stop:
RESULT = stop.value
break
근거
동기 제너레이터와의 관계
이 PEP는 동기 yield from의 의미와 매우 유사하도록 하는 것을 목표로 합니다. 다만 위임할 때 동기 제너레이터 메서드 대신 비동기 제너레이터 메서드가 사용된다는 점은 예외입니다. 이는 매우 직관적인 설계이며 동기 제너레이터와의 대칭성을 더욱 강화합니다.
문법으로서의 yield from 선택
이 PEP는 yield from을 문법으로 사용하지만, 명시적인 async 또는 await 표시 없이 비동기 컨텍스트 전환이 일어나므로 다소 논란의 여지가 있음을 인정합니다. 기각된 아이디어를 참조하십시오. 요컨대, yield from은 동기 제너레이터와 대칭을 이루고 입력하기 쉽기 때문에 가장 좋은 문법으로 선택되었습니다.
하위 호환성
이 PEP는 하위 호환되는 문법 변경을 도입합니다.
StopAsyncIteration에 value 속성을 추가하는 것은 기존 내장 예외에 대한 사소한 의미 변경이지만, StopIteration의 기존 value 속성을 그대로 따르며 StopAsyncIteration이나 비동기 이터레이터 프로토콜의 다른 동작에는 영향을 주지 않으므로, 실제로 기존 코드에 영향을 줄 가능성은 낮습니다.
보안 영향
이 PEP에는 알려진 보안 영향이 없습니다.
교육 방법
이 제안의 세부 사항은 다른 모든 언어 구문과 마찬가지로 Python 공식 문서에 수록될 것입니다. 하지만 이 PEP는 매우 직관적이도록 의도되었습니다. 사용자는 Python 제너레이터에 대한 자신의 배경지식만으로도 자연스럽게 yield from을 떠올릴 수 있어야 합니다.
참조 구현
이 PEP의 참조 구현은 python/cpython#145716에서 찾을 수 있습니다.
기각된 아이디어
async yield from을 문법으로 사용하기
현재 작성된 이 제안에 대한 주요 반론은 두 가지입니다.
- 공백이 아닌 첫 번째 토큰이
async나await가 아닌데도 비동기 작업을 수행하는 동작을 추가합니다. yield from이 사용되는 제너레이터의 종류에 따라 그 의미를 바꿉니다.
이러한 문제의 해결책은 비동기 하위 제너레이터 위임을 수행하는 새로운 async yield from 문을 도입하는 것이었습니다. 하지만 이 새로운 문법에는 그 나름의 문제들이 있었습니다.
무엇보다도 async yield from은 매우 장황합니다. Python에는 키워드 세 개를 연달아 사용하는 다른 문법 구문이 없습니다.
둘째, 짝이 되는 async yield가 없습니다. CPython은 이미 비동기 제너레이터 안의 yield 문에 대해 상당히 다른 바이트코드를 생성하므로, yield from도 그렇게 한다고 해서 무리한 일은 아닙니다.
셋째, 엄밀히 말하면 yield(yield from도 마찬가지)는 이미 비동기 컨텍스트 전환입니다. 제너레이터가 일시 중단되고, 호출자가 (asend()에 대해) await를 해야만 재개될 수 있기 때문입니다.
마지막으로, 평범한 yield from이 Python 사용자에게 더 대칭적이고 직관적입니다. 이해를 돕기 위해 다음 두 예를 살펴보십시오.
def gen():
yield 1
async def agen():
yield 1
def gen():
yield from subgen()
async def agen():
yield from asubgen()
async yield from은 이 대칭성을 깨뜨릴 것입니다.
그러나 전체적으로 보면 yield from과 async yield from의 차이는 주로 읽는 사람의 취향에 달려 있습니다. 이 PEP의 저자조차 어느 한쪽을 완전히 확신하지는 못합니다. 논의 중에 yield from과 async yield from의 차이는 수학에서 0^0을 무엇으로 정해야 하는지 결정하는 문제에 비유되었습니다. 이는 맥락과 관점에 따라 달라집니다. 대안은 비동기 하위 제너레이터로의 위임을 지원하지 않는 것이었는데, 이는 관련된 모든 당사자에게 손해만 되는 상황이었습니다. 결정은 내려야 했고, 합의에 결코 이르지 못하리라는 점은 분명했습니다.
async from, await from 및 이와 유사한 표기
async yield from의 장황함에 대한 대안으로, 일부는 장황함을 줄이기 위해 async from과 같은 표기를 사용하자고 제안했습니다. 안타깝게도 철자를 바꾸면 문법 전체의 가독성이 떨어질 가능성이 높으며, 따라서 async yield from을 지지하는 논거(인자)도 강화되지 않을 것입니다.
async yield from의 장점은 새로운 키워드를 도입하지 않고도 세 가지 중요한 부분을 각각 명시한다는 점입니다. 구체적으로는 다음과 같습니다.
async는 비동기 컨텍스트 전환을 암시하는 데 필요합니다.yield는 제너레이터가 일시 중단됨을 나타내는 데 필요합니다.from은 “표준” 제너레이터 일시 중단(yield문)과 하위 제너레이터 위임을 구별하는 데 필요합니다.
이 세 가지 제약을 고려하면, 더 간결한 표기가 존재할 가능성은 낮아 보입니다.
동기 하위 제너레이터로의 위임 허용
이 제안의 이전 개정판에서는 비동기 제너레이터 안의 yield from이 동기 제너레이터로 위임했습니다(그리고 async yield from이 비동기 하위 제너레이터 위임을 위한 짝이었습니다). 여기에는 여러 숨은 문제가 있었습니다.
특히 비동기 프레임과 동기 프레임을 섞으면 Python에 어울리지 않는 복잡성 계층이 생겼습니다. 구현에서는 동기 제너레이터 메서드와 비동기 제너레이터 메서드 사이에 숨겨진 변환 계층이 있어야 했을 것입니다. 즉, asend()에서 send()로, athrow()에서 throw()로, aclose()에서 close()로 변환해야 했을 것입니다.
서브제너레이터에 위임해야 하는 사람이라면 누구든 async yield from을 호출하기 전에 동기 Iterable이나 Generator를 비동기 버전으로 바꿔 주는 래퍼 클래스를 손쉽게 작성할 수 있습니다.
class AsAsyncIterator:
def __init__(self, wrapped):
self._wrapped = iter(wrapped)
def __aiter__(self):
return self
async def __anext__(self):
try:
return self._wrapped.__next__()
except StopAsyncIteration as e:
raise RuntimeError("async generator raised StopAsyncIteration") from e
except StopIteration as e:
raise StopAsyncIteration(e.value) from e
class AsAsyncGenerator(AsAsyncIterator):
async def asend(self, value):
try:
return self._wrapped.send(value)
except StopAsyncIteration as e:
raise RuntimeError("async generator raised StopAsyncIteration") from e
except StopIteration as e:
raise StopAsyncIteration(e.value) from e
async def athrow(self, exc):
try:
return self._wrapped.throw(exc)
except StopAsyncIteration as e:
raise RuntimeError("async generator raised StopAsyncIteration") from e
except StopIteration as e:
raise StopAsyncIteration(e.value) from e
async def aclose(self):
try:
return self._wrapped.close()
except StopAsyncIteration as e:
raise RuntimeError("async generator raised StopAsyncIteration") from e
async def agen():
yield from AsAsyncIterator([1, 2, 3])
yield from AsAsyncGenerator(subgen())
Brandt Bucher의 말을 인용하면 다음과 같습니다(의역):
그렇다면 차라리 동기 함수가 코루틴을 await할 수 있도록 허용하면 되지 않겠습니까?
또한 이 기능은 비동기 위임 지원에 비해 수요가 훨씬 적은 것으로 보였으므로, 현재로서는 이러한 문제를 해결하는 일의 우선순위가 낮습니다.
감사의 글
이 PEP의 참조 구현 개발을 도와준 Bartosz Sławecki에게 감사드립니다. 또한 비동기 제너레이터 내부에서 None이 아닌 반환값을 지원하는 작업과 함께 이루어진 StopAsyncIteration 변경 사항은 python/cpython#125401에 있는 Alex Dixon의 설계를 크게 참고했습니다.
방대한 피드백을 제공하고 설계와 구현에 대한 외부 의견도 수집해 준 Yury Selivanov에게 특별히 감사드립니다.
변경 이력
- 2026년 7월 18일
- 문법 선택을
async yield from에서yield from으로 변경했습니다.
- 문법 선택을
- 2026년 5월 26일
- (일반
yield from을 통해) 동기 서브제너레이터에 위임하는 기능에 대한 지원을 제거했습니다.
- (일반
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.