web-dev-qa-db-fra.com

@ asyncio.coroutine vs async def

Avec la bibliothèque asyncio que j'ai vue,

@asyncio.coroutine
def function():
    ...

et

async def function():
    ...

utilisé de manière interchangeable.

Y a-t-il une différence fonctionnelle entre les deux?

37
Jason

Oui, il existe des différences fonctionnelles entre les coroutines natives utilisant la syntaxe async def Et les coroutines basées sur le générateur utilisant le décorateur asyncio.coroutine.

Selon PEP 492 , qui introduit la syntaxe async def:

  1. Les objets coroutine natifs n'implémentent pas les méthodes __iter__ Et __next__. Par conséquent, ils ne peuvent pas être itérés ou passés à iter(), list(), Tuple() et à d'autres éléments intégrés. Ils ne peuvent pas non plus être utilisés dans une boucle for..in.

    Une tentative d'utilisation de __iter__ Ou __next__ Sur un objet coroutine natif entraînera une erreur TypeError.

  2. Les générateurs simples ne peuvent pas yield from coroutines natives : le faire entraînera une TypeError.

  3. coroutines basées sur générateur (pour le code asyncio doit être décoré avec @asyncio.coroutine) peut yield from objets coroutine natifs .

  4. inspect.isgenerator() et inspect.isgeneratorfunction() return False pour coroutine native objets et fonctions coroutines natives .

Le point 1 ci-dessus signifie que si les fonctions coroutine définies à l'aide de la syntaxe décoratrice @asyncio.coroutine Peuvent se comporter comme des fonctions génératrices traditionnelles, celles définies avec la syntaxe async def Ne le peuvent pas.

Voici deux fonctions coroutines minimales, ostensiblement équivalentes, définies avec les deux syntaxes:

import asyncio

@asyncio.coroutine
def decorated(x):
    yield from x 

async def native(x):
    await x 

Bien que le bytecode pour ces deux fonctions soit presque identique:

>>> import dis
>>> dis.dis(decorated)
  5           0 LOAD_FAST                0 (x)
              3 GET_YIELD_FROM_ITER
              4 LOAD_CONST               0 (None)
              7 YIELD_FROM
              8 POP_TOP
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE
>>> dis.dis(native)
  8           0 LOAD_FAST                0 (x)
              3 GET_AWAITABLE
              4 LOAD_CONST               0 (None)
              7 YIELD_FROM
              8 POP_TOP
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE

... la seule différence étant GET_YIELD_FROM_ITER vs GET_AWAITABLE, ils se comportent complètement différemment lors d'une tentative d'itération sur les objets qu'ils retournent:

>>> list(decorated('foo'))
['f', 'o', 'o']
>>> list(native('foo'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'coroutine' object is not iterable

Évidemment, 'foo' N'est pas un fichier attendu, donc la tentative d'appeler native() avec lui n'a pas beaucoup de sens, mais le point est, espérons-le, clair que l'objet coroutine qu'il renvoie n'est pas itérable, quel que soit son argument.

Une étude plus détaillée de la syntaxe async/await par Brett Cannon: Comment diable l'async/attente fonctionne-t-il dans Python 3.5? couvre cette différence plus en profondeur.

43
Zero Piraeus

async def est une nouvelle syntaxe de Python 3.5. Vous pouvez utiliser await, async with et async for à l'intérieur async defs.

@coroutine est un analogue fonctionnel de async def mais cela fonctionne en Python 3.4+ et utilise yield from construction au lieu de await.

Pour une perspective pratique, n'utilisez jamais @coroutine si votre Python est 3,5+.

15
Andrew Svetlov

De Python 3.5coroutines est officiellement devenu un type distinct et donc le async def syntaxe, ainsi que les instructions await.

Avant cela, Python 3.4 créait des coroutines en encapsulant des fonctions régulières dans generators, d'où la syntaxe du décorateur et la plus semblable à un générateur yield from.

3
songololo