8.10. Čekljive klase

8.10.1. Što je čekljiv objekt

Kada korutina napiše await x, jezik pita x kako da ga pričeka. Svaki objekt koji zna odgovoriti je čekljiv. Postoje dvije vrste:

  • Objekti korutina koje vraćaju funkcije async def. Pozivanje send_request() svaki put proizvodi jedan od njih – objekt korutine, a ne rezultat funkcije. Pisanje await send_request() je ono što zapravo pokreće tijelo.

  • Objekti klase koja definira metodu __await__. async def je kratica za prvu vrstu; __await__ je ručni način za stvaranje druge vrste.

Kôd aplikacije prema zadanim postavkama koristi prvu vrstu. Druga vrsta postoji za rijedak slučaj kada sam objekt treba biti ono što pozivatelj awaita, a ne rezultat pozivanja metode nad njim.

8.10.2. Dva oblika jedan uz drugi

Ista logika napisana kao korutina i kao čekljiva klasa:

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

Oba se mogu awaitati na isti način:

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

Oblik async def je kraći, čita se poput uobičajenog Pythona i prepušta jeziku da upravlja cijelim knjigovodstvom obustave i nastavka. Oblik s klasom u ovom primjeru ne dobiva ništa dodatno.

8.10.3. Kada oblik s klasom zaslužuje svoje mjesto

Kada objekt mora biti nešto što aplikacija proslijeđuje – a ne funkcija koju pozivatelj poziva da bi dobio korutinu – oblik s klasom je jedina opcija:

  • Vrijednost nalik futuru koju aplikacija stvara, predaje proizvođaču i kasnije awaita kako bi dohvatila proizvedeni rezultat.

  • Prilagođeni primitiv izgrađen na postojećem gdje je prirodni API await my_thing umjesto await my_thing.wait(). Sama klasa asyncio.Task ugrađeni je primjer – pisanje await task funkcionira jer Task definira __await__.

Za sve što odgovara obliku pozovi funkciju, dobij korutinu, počekaj je, async def pobjeđuje.

8.10.4. Pojedinosti protokola

__await__ je obična metoda (ne async def) koja vraća iterator. Pisanje u obliku generatora – uključujući barem jedan yield u tijelu – automatski ga čini takvim. Svaki yield obustavlja korutinu koja awaita objekt i predaje kontrolu natrag petlji događaja; generator se nastavlja sljedeći put kad petlja rasporedi zadatak. Goli return (ili kraj tijela) završava čekanje; što god return proizvede postaje vrijednost izraza await.

U praksi rijetka klasa koja to čini prepušta prepuštanje kontrole postojećem čekljivom objektu umjesto da sama pogoni petlju:

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

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

yield from ponovno koristi __await__ osnovnog događaja za stvarno obustavljanje. Većini asyncio aplikacija nikada ne treba pisati nijedan od ova dva oblika.