8.10. Awaitable-Klassen

8.10.1. Was ein Awaitable ist

Wenn eine Koroutine await x schreibt, fragt die Sprache x, wie darauf gewartet werden soll. Jedes Objekt, das diese Frage beantworten kann, ist awaitable. Es gibt zwei Arten:

  • Die Koroutinen-Objekte, die async def-Funktionen zurückgeben. Der Aufruf von send_request() erzeugt jedes Mal eines davon – das Koroutinen-Objekt, nicht das Ergebnis der Funktion. Das Schreiben von await send_request() ist es, was den Rumpf tatsächlich ausführt.

  • Objekte einer Klasse, die eine __await__-Methode definiert. async def ist die Kurzschreibweise für die erste Art; __await__ ist der manuelle Weg, die zweite Art zu erzeugen.

Anwendungscode verwendet standardmäßig die erste Art. Die zweite Art existiert für den seltenen Fall, dass das Objekt selbst das sein muss, worauf der Aufrufer awaitet, und nicht das Ergebnis des Aufrufs einer Methode darauf.

8.10.2. Die beiden Formen nebeneinander

Dieselbe Logik, einmal als Koroutine und einmal als Awaitable-Klasse geschrieben:

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

Auf beide kann auf dieselbe Weise awaitet werden:

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

Die async def-Form ist kürzer, liest sich wie normales Python und überlässt der Sprache die gesamte Verwaltung des Anhaltens und Fortsetzens. Die Klassenform bringt in diesem Beispiel keinen zusätzlichen Nutzen.

8.10.3. Wann sich die Klassenform lohnt

Wenn das Objekt etwas sein muss, das die Anwendung weiterreicht – und keine Funktion, die der Aufrufer aufruft, um eine Koroutine zu erhalten – ist die Klassenform die einzige Möglichkeit:

  • Ein future-ähnlicher Wert, den die Anwendung erzeugt, an einen Produzenten übergibt und später awaitet, um das erzeugte Ergebnis abzurufen.

  • Ein eigenes Primitiv, das auf einem bestehenden aufbaut und bei dem die natürliche API await my_thing statt await my_thing.wait() lautet. Die Klasse asyncio.Task selbst ist ein eingebautes Beispiel – await task funktioniert, weil Task __await__ definiert.

Für alles, was zur Form eine Funktion aufrufen, eine Koroutine erhalten, sie awaiten passt, gewinnt async def.

8.10.4. Die Details des Protokolls

__await__ ist eine reguläre Methode (kein async def), die einen Iterator zurückgibt. Sie als Generator zu schreiben – mit mindestens einem yield im Rumpf – macht sie automatisch dazu. Jedes yield hält die Koroutine an, die das Objekt awaitet, und gibt die Kontrolle an die Ereignisschleife zurück; der Generator wird fortgesetzt, sobald die Schleife den Task das nächste Mal einplant. Ein einfaches return (oder das Erreichen des Endes) beendet das Warten; was auch immer das return liefert, wird zum Wert des await-Ausdrucks.

In der Praxis übergibt die seltene Klasse, die dies tut, das Yielding an ein bestehendes Awaitable, anstatt die Schleife selbst anzutreiben:

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

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

Das yield from verwendet das __await__ des zugrunde liegenden Events für das eigentliche Anhalten wieder. Die meisten asyncio-Anwendungen müssen niemals eine der beiden Formen schreiben.