PEP 0492 ajoute la nouvelle méthode magique __await__
. L'objet qui implémente cette méthode devient objet de type futur et peut être attendu avec await
. C'est clair:
import asyncio
class Waiting:
def __await__(self):
yield from asyncio.sleep(2)
print('ok')
async def main():
await Waiting()
if __== "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Ok, mais si je veux appeler une fonction définie par async def
au lieu de asyncio.sleep
? Je ne peux pas utiliser await
car __await__
n'est pas une fonction async
, je ne peux pas utiliser yield from
car les coroutines natives nécessitent await
expression:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
yield from new_sleep() # this is TypeError
await new_sleep() # this is SyntaxError
print('ok')
Comment puis-je le résoudre?
Version abrégée: await foo
peut être remplacé par yield from foo.__await__()
Combinant toutes les idées des autres réponses -
dans le cas le plus simple, il suffit de déléguer à d'autres travaux en attente:
def __await__(self):
return new_sleep().__await__()
Cela fonctionne car la méthode __await__
renvoie un itérateur (voir PEP 492 ), aussi, renvoyer un autre itérateur __await__
convient.
Cela signifie, bien sûr, que nous ne pouvons pas du tout changer le comportement de suspension de l'original. L’approche la plus générale consiste à reproduire le mot clé await
et à utiliser yield from
, ce qui nous permet de combiner plusieurs itérateurs en attente en un seul:
def __await__(self):
# theoretically possible, but not useful for my example:
#yield from something_else_first().__await__()
yield from new_sleep().__await__()
Voici le piège: cela ne fait pas exactement la même chose que la première variante! yield from
est une expression, donc pour faire exactement comme avant, nous devons aussi renvoyer cette valeur:
def __await__(self):
return (yield from new_sleep().__await__())
Cela reflète directement comment nous écririons la délégation appropriée en utilisant la syntaxe await
:
return await new_sleep()
extra bit - quelle est la différence entre ces deux?
def __await__(self):
do_something_synchronously()
return new_sleep().__await__()
def __await__(self):
do_something_synchronously()
return (yield from new_sleep().__await__())
La première variante est une fonction simple: lorsque vous l'appelez, do_...
est exécuté et un itérateur est renvoyé. La seconde est une fonction génératrice; l'appeler n'exécute aucun de nos codes! Ce n'est que lorsque l'itérateur renvoyé est généré pour la première fois que do_...
sera exécuté. Cela fait une différence dans ce qui suit, une situation un peu artificielle:
def foo():
tmp = Waiting.__await__()
do_something()
yield from tmp
Je ne comprenais pas pourquoi je ne pouvais pas céder avec la coroutine native dans __await__
, mais il semble possible de céder avec le générateur coroutine dans __await__
et céder avec la coroutine native dans cette générateur de coroutine. Ça marche:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
@asyncio.coroutine
def wrapper(coro):
return (yield from coro)
return (yield from wrapper(new_sleep()))
Pour attendre dans une fonction __await__
, utilisez le code suivant:
async def new_sleep():
await asyncio.sleep(1)
class Waiting:
def __await__(self):
yield from new_sleep().__await__()
print('first sleep')
yield from new_sleep().__await__()
print('second sleep')
return 'done'
Utilisez un décorateur.
def chain__await__(f):
return lambda *args, **kwargs: f(*args, **kwargs).__await__()
Ensuite, écrivez __await__
en tant que coroutine native.
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
@chain__await__
async def __await__(self):
return await new_sleep()
Vous pouvez également simplifier la version de Mikhail à ceci:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
async def wrapper():
await new_sleep()
print("OK")
return wrapper()
Votre code original a bien fonctionné sous Python 3.6.
Les corrections dans les autres réponses sont impressionnantes, je ne pouvais même pas croire que certaines d’entre elles fonctionnaient (mais c’est le cas!), Mais je crains que si le modèle asyncio
ne cesse de changer, ces corrections ne fonctionneront plus.
J'ai un correctif minimaliste, qui fonctionne sur 3.7, sans wrapper:
import asyncio
class Waiting:
def __await__(self):
yield from asyncio.sleep(2).__await__()
print('ok')
async def main():
await Waiting()
if __== "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
La seule différence par rapport au code d'origine consiste à ajouter .__await__()
à asyncio.sleep(2)
, même sans wrapper.
Vous pouvez également nous utiliser ce wrapper sync_await
autour du générateur que vous voulez await
dans __await__
, juste comme ça:
import asyncio
def sync_await(gen):
if hasattr(gen, '__await__'):
# 3.7, and user defined coroutines in 3.6
print('yield from gen.__await__()')
return (yield from gen.__await__())
else:
# 3.6, only native coroutines like asyncio.sleep()
print('yield from gen')
return (yield from gen)
class Waiting:
def __await__(self):
yield from sync_await(asyncio.sleep(2))
print('ok')
async def main():
await Waiting()
if __== "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Remarque - le wrapper ne fait pas return (yield from ...)
, mais yield from
- une délégation d'itérateur de générateur simple.