8.10. Klasy awaitable#
8.10.1. Czym jest obiekt awaitable#
Gdy korutyna zapisze await x, język pyta x, jak na niego czekać. Każdy obiekt, który potrafi na to odpowiedzieć, jest awaitable. Istnieją dwa rodzaje:
Obiekty korutyn zwracane przez funkcje
async def. Wywołaniesend_request()za każdym razem produkuje jeden z nich – obiekt korutyny, a nie wynik funkcji. Zapisawait send_request()to to, co faktycznie uruchamia ciało funkcji.Obiekty klasy definiującej metodę
__await__.async defjest skrótem dla pierwszego rodzaju;__await__to ręczny sposób tworzenia drugiego rodzaju.
Kod aplikacji domyślnie korzysta z pierwszego rodzaju. Drugi rodzaj istnieje dla rzadkiego przypadku, gdy to sam obiekt ma być tym, na co wywołujący wykonuje await, a nie wynik wywołania metody na nim.
8.10.2. Obie formy obok siebie#
Ta sama logika zapisana jako korutyna i jako klasa 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
Na obie można wykonać await w ten sam sposób:
async def main():
a = await yield_then_return(1)
b = await YieldThenReturn(2)
print(a, b) # prints: 1 2
Forma async def jest krótsza, czyta się jak zwykły Python i pozwala językowi zarządzać całą buchalterią wstrzymywania i wznawiania. Forma klasowa w tym przykładzie nie daje nic dodatkowego.
8.10.3. Kiedy forma klasowa zasługuje na swoje miejsce#
Gdy obiekt musi być czymś, co aplikacja przekazuje dalej – a nie funkcją, którą wywołujący wywołuje, by otrzymać korutynę – jedyną opcją jest forma klasowa:
Wartość future-like, którą aplikacja tworzy, przekazuje producentowi i na którą później wykonuje
await, aby pobrać wytworzony wynik.Niestandardowy prymityw zbudowany na bazie istniejącego, w którym naturalnym API jest
await my_thingzamiastawait my_thing.wait(). Sama klasaasyncio.Taskjest wbudowanym przykładem – zapisawait taskdziała, ponieważTaskdefiniuje__await__.
Dla wszystkiego, co pasuje do schematu wywołaj funkcję, otrzymaj korutynę, wykonaj na niej await, wygrywa async def.
8.10.4. Szczegóły protokołu#
__await__ to zwykła metoda (nie async def), która zwraca iterator. Zapisanie jej jako generatora – z co najmniej jednym yield w ciele – automatycznie czyni ją takim. Każdy yield wstrzymuje korutynę wykonującą await na obiekcie i oddaje sterowanie z powrotem do pętli zdarzeń; generator wznawia działanie przy następnym zaplanowaniu zadania przez pętlę. Zwykłe return (lub dotarcie do końca) kończy oczekiwanie; cokolwiek return wytworzy, staje się wartością wyrażenia await.
W praktyce rzadka klasa, która to robi, oddaje yieldowanie istniejącemu obiektowi awaitable, zamiast samodzielnie napędzać pętlę:
class WaitForEvent:
def __init__(self, event):
self._event = event
def __await__(self):
yield from self._event.wait().__await__()
return self._event
yield from ponownie wykorzystuje __await__ bazowego zdarzenia do faktycznego wstrzymywania. Większość aplikacji asyncio nigdy nie musi zapisywać żadnej z tych form.