8.10. Awaitable 클래스

8.10.1. awaitable이란 무엇인가

코루틴이 await x를 작성하면, 언어는 x에게 어떻게 대기할지를 묻습니다. 이에 답하는 방법을 아는 모든 객체는 awaitable입니다. 두 가지 종류가 있습니다:

  • async def 함수가 반환하는 코루틴 객체입니다. send_request()를 호출하면 매번 이 중 하나가 생성됩니다 – 함수의 결과가 아니라 코루틴 객체입니다. await send_request()를 작성하는 것이 실제로 본문을 실행하는 일입니다.

  • __await__ 메서드를 정의한 클래스의 객체입니다. async def는 첫 번째 종류의 축약 표현이고, __await__는 두 번째 종류를 직접 만드는 방법입니다.

애플리케이션 코드는 기본적으로 첫 번째 종류를 사용합니다. 두 번째 종류는 메서드를 호출한 결과가 아니라 객체 자체가 호출자가 await할 대상이 되어야 하는 드문 경우를 위해 존재합니다.

8.10.2. 두 형태의 나란한 비교

동일한 로직을 코루틴으로 작성한 것과 awaitable 클래스로 작성한 것입니다:

import asyncio

# async def -- almost always the right choice
async def yield_then_return(value):
    await asyncio.sleep_ms(0)
    return value

# awaitable class -- equivalent, rarely written
class YieldThenReturn:
    def __init__(self, value):
        self._value = value

    def __await__(self):
        yield                       # let the loop run once
        return self._value          # value of the ``await`` expression

둘 다 같은 방식으로 await할 수 있습니다:

async def main():
    a = await yield_then_return(1)
    b = await YieldThenReturn(2)
    print(a, b)                     # prints: 1 2

async def 형태는 더 짧고, 일반적인 Python처럼 읽히며, 모든 일시 중단 및 재개 관리를 언어에 맡깁니다. 이 예제에서 클래스 형태는 추가로 얻는 것이 없습니다.

8.10.3. 클래스 형태가 제 역할을 하는 경우

객체가 호출자가 코루틴을 얻기 위해 호출하는 함수가 아니라, 애플리케이션이 주고받는 무언가가 되어야 할 때는 클래스 형태가 유일한 선택지입니다:

  • 애플리케이션이 생성하여 생산자에게 넘긴 뒤 나중에 await해서 생산된 결과를 가져오는 future와 유사한 값.

  • 기존 프리미티브 위에 만들어진 사용자 정의 프리미티브로, 자연스러운 API가 await my_thing.wait()가 아니라 await my_thing인 경우입니다. asyncio.Task 클래스 자체가 내장된 예시입니다 – await task가 동작하는 이유는 Task__await__를 정의하기 때문입니다.

함수를 호출하고, 코루틴을 얻고, 이를 await한다는 형태에 맞는 모든 것에는 async def가 낫습니다.

8.10.4. 프로토콜 세부사항

__await__는 이터레이터를 반환하는 일반 메서드입니다(async def가 아닙니다). 본문에 최소한 하나의 yield를 포함하는 제너레이터로 작성하면 자동으로 이터레이터가 됩니다. 각 yield는 그 객체를 await하는 코루틴을 일시 중단하고 제어권을 이벤트 루프로 돌려줍니다. 제너레이터는 루프가 다음에 태스크를 스케줄링할 때 재개됩니다. 빈 return(또는 끝에 다다르는 것)은 대기를 끝냅니다. return이 생성하는 값이 무엇이든 await 표현식의 값이 됩니다.

실제로 이를 수행하는 드문 클래스는 루프를 직접 구동하기보다 yield 작업을 기존 awaitable에 넘깁니다:

class WaitForEvent:
    def __init__(self, event):
        self._event = event

    def __await__(self):
        yield from self._event.wait().__await__()
        return self._event

yield from은 실제 일시 중단을 위해 기반 이벤트의 __await__를 재사용합니다. 대부분의 asyncio 애플리케이션은 두 형태 중 어느 것도 작성할 필요가 없습니다.