Ein Vorgeschmack auf Async
Eine Coroutine ausführen
Lektion 1 endete mit einer Regel: Ein Handler auf einer async def und await, und führt eine Coroutine mit asyncio.run auf dem Server aus, wo du zusehen kannst.
async def erstellt eine Coroutine-Funktion
async def label(snapshot):
return f"version {snapshot['version']}"
Das einzige neue Wort ist async, und es ändert, was ein Aufruf tut. Der Aufruf label({"version": 3}) führt den Rumpf nicht aus. Er gibt ein Coroutine-Objekt zurück: den Aufruf, vorbereitet und vor seiner ersten Zeile angehalten, wartend darauf, dass ihn etwas ausführt. Noch hat nichts "version 3" berechnet. Ein Coroutine-Objekt, das du nicht ausführen willst, kannst du mit seiner close() wegwerfen.
Was gibt das aus?
async def label(version):
return f"version {version}"
pending = label(3)
print(type(pending).__name__)
pending.close()
Der coroutine. Kein "version 3" erscheint, weil der Rumpf nie gelaufen ist.
Das Schlüsselwort await
Innerhalb einer anderen async def führst du eine Coroutine mit await aus:
async def shout(snapshot):
line = await label(snapshot)
return line.upper()
await label(snapshot) führt die Coroutine von label bis zu ihrem Ende aus und gibt ihren shout anhalten kann. Muss das Abgewartete warten, etwa ein Senden an einen langsamen Zuschauer oder der nächste Snapshot, hält die Coroutine beim await an, die Loop führt andere Coroutinen aus und setzt diese später genau dort fort, wo sie angehalten hat. label muss nie warten, also kehrt das await hier einfach zurück. Jedes await ist eine Stelle, an der die Kontrolle an die Loop zurückgehen kann, und das ist die Regel aus Lektion 1, in den Code geschrieben.
await gehört nur in eine async def. An jeder anderen Stelle lehnt Python es ab, bevor auch nur eine Zeile läuft:
def shout(snapshot):
line = await label(snapshot)
return line.upper()
Die Meldung lautet SyntaxError: 'await' outside async function, und auf der obersten Ebene eines 'await' outside function. Ein gewöhnliches def kann also nicht abwarten. Eine Funktion, die abwartet, muss selbst eine Coroutine-Funktion sein, und ebenso alles, was sie abwartet, bis hinauf zu der einen äußersten Coroutine, die etwas von außen startet.
Wo darfst du await label(snapshot) schreiben?
asyncio.run startet die Loop
Etwas muss die äußerste Coroutine ausführen. In der Standardbibliothek ist das asyncio.run:
import asyncio
print(asyncio.run(shout({"version": 3})))
Es erstellt eine Event-Loop, führt die übergebene Coroutine bis zu ihrem Ende aus, zusammen mit allem, was diese Coroutine abwartet, gibt den Rückgabewert der Coroutine zurück und schließt die Loop. Ein Programm ruft es normalerweise einmal auf, ganz oben, und alles andere wird von dort aus abgewartet.
Ein Aufruf von asyncio.run, während schon eine Loop läuft, zum Beispiel aus einer Coroutine heraus, die asyncio.run gestartet hat, löst RuntimeError: asyncio.run() cannot be called from a running event loop aus. Innerhalb einer Coroutine ist await other() der Weg, eine andere auszuführen.
Dieser Fehler ist der Grund, warum kein Beispiel auf dieser Seite asyncio.run aufruft. Die Beispiele, die du auf einer Seite dieser Website ausführen kannst, verwenden eine Python-Version, die für den Browser gebaut ist, und es heißt, dass sie in der eigenen Event-Loop der Seite läuft, wo asyncio.run genau diesen Fehler auslösen würde. Dieser Kurs hat das nicht überprüft, also verlässt er sich auf keine der beiden Antworten: Jedes asyncio.run in diesem Kapitel läuft auf dem Server, im Run dieser und der nächsten Lektion.
Eine Coroutine, die schon unter asyncio.run läuft, ruft asyncio.run(other()) auf. Was passiert?
await vergessen
Eine Coroutine, die nie abgewartet wird, läuft nie. Python bemerkt, wenn so ein Coroutine-Objekt weggeworfen wird, und gibt eine Warnung aus, die es nennt:
RuntimeWarning: coroutine 'label' was never awaited
Das Programm macht ohne das Ergebnis weiter, also zeigt sich ein fehlendes await als
Diese Lektion führt eine Coroutine nach der anderen aus, jede der Reihe nach abgewartet. asyncio kann auch mehrere Coroutinen als
In two_labels schreibst du label(first) ohne await. Was bekommt two_labels?
Probier es aus
Dieser Teil ist optional, und nichts prüft ihn. Öffne async_scoreboard.py, die Datei, in die die Arbeit dieses Kapitels kommt. Schreibe label wie oben und schreibe two_labels(first, second): Beginne mit einer leeren await label(first) an, hänge await label(second) an und gib die Liste zurück.
Klicke auf Run. Es führt async_demo.py aus, das deine Coroutinen mit asyncio.run auf dem Server aufruft. Sein Abschnitt zu Lektion 2 ruft zuerst label(...) auf, ohne es auszuführen, und schließt die Coroutine, die es bekommen hat, und führt dann beide deiner Coroutinen aus. Eine fertige Version hat auf der Seite ausgegeben:
--- Lesson 2: a coroutine runs only when something runs it
calling label(...) alone gives a coroutine and runs nothing yet
asyncio.run(label(...)) gives 'version 3'
asyncio.run(two_labels(...)) gives ['version 4', 'version 5']
--- Lesson 3: your handler with a stand-in connection
not written yet
--- Lesson 3: your handler served by websockets.asyncio
pl_ws.py, a program written for this course, serves your handler with websockets.asyncio and plays three viewers:
async_scoreboard.viewer_handler is not written yet (Lesson 3)
Das Run braucht die Bibliothek websockets, weil das vorgegebene async_scoreboard.py und pl_ws.py sie oben importieren; die Sandbox der Seite hat sie. Die erste Zeile kommt, bevor irgendetwas von dir läuft, sie erscheint also auch, wenn du diesen Teil überspringst; bis du label schreibst, lautet die nächste Zeile not written yet. Die Abschnitte zu Lektion 3 warten auf den Handler, den du als Nächstes schreibst.
In der nächsten Lektion wird der Zuschauer-Handler der Anzeigetafel zu einer Coroutine, und sein Warten wird zu einem await.