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

Python 개선 제안 한국어 번역

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

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 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동작을 요청하는 별개의 요청이 세 건 있었습니다. 이 동작들은 서로 밀접하게 관련되어 있습니다:

  1. https://discuss.python.org/t/8897
  2. https://discuss.python.org/t/47050
  3. https://discuss.python.org/t/66886

또한 Stack Overflow의 사용자들은 이 설계 결정에 의문을 제기했습니다(questioned).

서브제너레이터 위임은 비동기 제너레이터에 유용합니다

비동기 제너레이터에서 yield from 지원이 없는 현재의 대안은 각 항목을 수동으로 생성하는 for/async for 루프를 사용하는 것입니다. 여기에는 몇 가지 단점이 있습니다:

  1. 각 위임 지점이 루프가 되므로 코드의 의도가 모호해지고 비동기 제너레이터를 다루는 데 필요한 작업량이 증가합니다. 이는 비동기 제너레이터의 강력한 기능을 훼손합니다.
  2. asend(), athrow()aclose()는 호출자와 제대로 상호작용하지 않습니다. 이것이 애초에 yield from이 추가된 주된 이유입니다.
  3. 반환 값은 비동기 제너레이터에서 기본적으로 지원되지 않습니다. 이에 대한 대안은 예외를 발생시키는 것이며, 이로 인해 보일러플레이트 코드가 늘어납니다.

명세

컴파일러 변경 사항

컴파일러는 더 이상 비동기 제너레이터 내부의 SyntaxError에 대해 return 또는 yield from 문에서 오류를 발생시키지 않습니다.

StopAsyncIteration 변경 사항

관련 StopAsyncIteration예외에는 비동기 제너레이터의 yield from 표현식 결과로 사용될 새로운 value 어트리뷰트가 추가됩니다.

이 속성은 StopAsyncIteration에 위치 인자를 전달하여 지정할 수 있습니다. 예를 들면:

>>> exception = StopAsyncIteration(42)
>>> exception.value
42

인자를 지정하지 않으면 valueNone입니다.

비동기 제너레이터 내부의 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는 하위 호환 가능한 구문 변경을 도입합니다.

기존 내장 예외인 StopAsyncIterationvalue 속성을 추가하는 것은 사소한 의미적 변경이지만, 기존 코드에 실제로 영향을 미칠 가능성은 낮습니다. 기존 StopIterationvalue 속성을 반영하며 StopAsyncIteration 또는 비동기 이터레이터 프로토콜의 다른 동작에는 영향을 주지 않기 때문입니다.

보안 관련 사항

이 PEP에는 알려진 보안 관련 사항이 없습니다.

이 내용을 가르치는 방법

이 제안의 세부 사항은 다른 모든 언어 구성 요소와 마찬가지로 Python의 표준 문서에 수록됩니다. 그러나 이 PEP는 매우 직관적인 것을 목표로 하므로, 사용자는 Python의 제너레이터에 관한 기존 지식을 바탕으로 자연스럽게 yield from을 사용할 수 있어야 합니다.

참조 구현

이 PEP의 참조 구현은 python/cpython#145716에서 확인할 수 있습니다.

거부된 아이디어

구문으로 async yield from 사용하기

현재 작성된 이 제안에 반대하는 주요 논거는 두 가지입니다.

  1. 첫 번째 공백이 아닌 토큰으로 asyncawait를 사용하지 않고도 비동기 작업을 수행하는 동작을 추가합니다.
  2. 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 fromasync yield from의 차이는 주로 독자의 취향에 달려 있습니다. 이 PEP의 작성자조차 어느 한쪽을 완전히 확신하지는 못합니다. 논의 중에 yield fromasync yield from의 차이는 수학에서 0^0이 무엇이어야 하는지 결정하는 것에 비유되었습니다. 이는 맥락과 관점에 따라 달라집니다. 대안은 비동기 하위 제너레이터에 대한 위임 지원을 추가하지 않는 것이었지만, 이는 관련된 모든 당사자에게 손해인 상황이었습니다. 결정을 내려야 했고, 합의에 도달할 수 없다는 점은 분명했습니다.

async from, await from 및 이와 유사한 표기

async yield from의 장황함에 대한 대안으로, 일부는 장황함을 줄이기 위해 async from과 같은 표기를 사용하자고 제안했습니다. 안타깝게도 표기를 변경하면 구문 전체의 가독성이 저하될 가능성이 높으므로, async yield from을 지지하는 논거도 개선되지 않습니다.

async yield from의 장점은 새로운 키워드를 도입하지 않고도 세 가지 중요한 부분을 각각 명시한다는 점입니다. 구체적으로는 다음과 같습니다.

  1. async는 비동기 컨텍스트 전환을 암시하는 데 필요합니다.
  2. yield는 제너레이터가 일시 중단됨을 나타내는 데 필요합니다.
  3. from은 “일반적인” 제너레이터 일시 중단(yield 문)과 하위 제너레이터 위임을 구별하는 데 필요합니다.

이 세 가지 제약을 고려하면 더 간결한 표기가 존재할 가능성은 작아 보입니다.

동기 하위 제너레이터에 대한 위임 허용

이 제안의 이전 개정판에서는 비동기 제너레이터의 yield from이 동기 제너레이터에 위임했으며, async yield from은 비동기 하위 제너레이터 위임에 대응하는 구문이었습니다. 여기에는 여러 숨은 문제가 있었습니다.

특히 비동기 프레임과 동기 프레임을 혼합하면 Python에 적합하지 않은 복잡성 계층이 추가되었습니다. 구현에서는 동기 제너레이터 메서드와 비동기 제너레이터 메서드 사이에 숨겨진 변환 계층이 필요했을 것입니다. 즉, asend()에서 send()로, athrow()에서 throw()로, aclose()에서 close()로 변환해야 했을 것입니다.

하위 제너레이터에 위임해야 하는 사람이라면 누구나 동기식 Iterable 또는 Generator를 비동기식으로 업그레이드하는 래퍼 클래스를 작성한 후 async yield from을 호출하는 것은 간단합니다.

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의 말을 빌리면(의역):

그렇다면 동기식 함수가 코루틴을 기다릴 수 있도록 허용하면 되지 않겠습니까?

또한 비동기 위임 지원에 비해 이 기능에 대한 수요가 훨씬 적어 보였으므로, 당분간 이러한 문제를 해결하는 일의 우선순위는 낮습니다.

감사의 말

이 PEP의 참조 구현 개발을 지원해 주신 Bartosz Sławecki에게 감사드립니다. 또한 비동기 제너레이터 내부에서 None이 아닌 반환 값을 지원하는 변경 사항과 함께 이루어진 StopAsyncIteration변경은 대부분 Alex Dixon이 python/cpython#125401에서 제안한 설계를 기반으로 합니다.

광범위한 의견을 제공하고 설계 및 구현에 관한 외부 의견도 수렴해 주신 Yury Selivanov에게 특별히 감사드립니다.

변경 이력

  • 2026년 7월 18일
    • 구문으로 async yield from대신 yield from을 선택하도록 변경했습니다.
  • 2026년 5월 26일
    • 동기식 하위 제너레이터에 위임하는 기능을 제거했습니다(일반 yield from을 통해).