8.10. Awaitable osztályok

8.10.1. Mi az awaitable

Amikor egy korutin await x-et ír, a nyelv megkérdezi x-től, hogyan kell rá várni. Bármely objektum, amely tudja, hogyan válaszoljon, awaitable. Két fajta van:

  • Azok a korutinobjektumok, amelyeket az async def függvények adnak vissza. A send_request() hívása minden alkalommal ezek egyikét állítja elő – a korutinobjektumot, nem a függvény eredményét. Az await send_request() írása az, ami valójában lefuttatja a törzset.

  • Egy olyan osztály objektumai, amely definiál egy __await__ metódust. Az async def az első fajta rövidítése; az __await__ a kézi módja a második fajta létrehozásának.

Az alkalmazáskód alapértelmezetten az első fajtát használja. A második fajta arra a ritka esetre létezik, amikor magának az objektumnak kell annak lennie, amire a hívó await-tel vár, nem pedig egy rajta meghívott metódus eredményének.

8.10.2. A két forma egymás mellett

Ugyanaz a logika korutinként és awaitable osztályként megírva:

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

Mindkettő ugyanúgy await-elhető:

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

Az async def forma rövidebb, úgy olvasható, mint a normál Python, és lehetővé teszi, hogy a nyelv kezelje az összes felfüggesztési és folytatási könyvelést. Az osztályforma ebben a példában semmi extrát nem nyújt.

8.10.3. Mikor érdemes az osztályformát választani

Amikor az objektumnak lennie kell valaminek, amit az alkalmazás körbeadogat – nem pedig egy függvénynek, amelyet a hívó meghív, hogy korutint kapjon –, az osztályforma az egyetlen lehetőség:

  • Egy future-szerű érték, amelyet az alkalmazás létrehoz, átad egy termelőnek, majd később await-tel vár rá, hogy lekérje az előállított eredményt.

  • Egy meglévő primitívre épülő egyedi primitív, ahol a természetes API az await my_thing, nem pedig az await my_thing.wait(). Maga az asyncio.Task osztály egy beépített példa – az await task írása azért működik, mert a Task definiálja az __await__-et.

Bármire, ami a hívj meg egy függvényt, kapj egy korutint, várj rá formába illik, az async def a nyerő.

8.10.4. A protokoll részletei

Az __await__ egy hagyományos metódus (nem async def), amely egy iterátort ad vissza. Ha generátorként írjuk meg – legalább egy yield-et téve a törzsbe –, az automatikusan azzá teszi. Minden yield felfüggeszti az objektumra await-tel váró korutint, és visszaadja a vezérlést az eseményhuroknak; a generátor akkor folytatódik, amikor a hurok legközelebb ütemezi a feladatot. Egy önálló return (vagy a végére érés) befejezi a várakozást; amit a return előállít, az lesz az await kifejezés értéke.

A gyakorlatban az a ritka osztály, amely ezt teszi, a yield-elést egy meglévő awaitable-re bízza, ahelyett hogy maga működtetné a hurkot:

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

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

A yield from újrahasznosítja a mögöttes esemény __await__-jét a tényleges felfüggesztéshez. A legtöbb asyncio-alkalmazásnak soha nincs szüksége egyik forma megírására sem.