Spécification précise de __await__
La référence du langage Python spécifie ce object.__await__qui suit:
object.__await__(self)Doit retourner un itérateur. Doit être utilisé pour implémenter des objets attendables. Par exemple,
asyncio.Futureimplémente cette méthode pour être compatible avec l'expression await.
C'est ça. Je trouve cette spécification très vague et pas très précise (ironiquement). Ok, il devrait renvoyer un itérateur, mais peut-il être un itérateur arbitraire? Évidemment pas:
import asyncio
class Spam:
def __await__(self):
yield from range(10)
async def main():
await Spam()
asyncio.run(main())
RuntimeError: Task got bad yield: 0
Je suppose que la asyncioboucle d'événements s'attend à ce qu'un type spécifique d'objet soit produit par l'itérateur. Alors, que devrait-il donner exactement ? (Et pourquoi n'est-ce pas documenté?)
Edit: pour autant que je puisse voir, cela n'est documenté nulle part. Mais je suis enquêtait sur moi - même, et je pense que la clé pour comprendre quels objets l' asyncioattend de ses coroutines pour donner réside dans task_step_impldans _asynciomodule.c.
Mise à jour: J'ai fait un PR dans le référentiel cpython dans le but de clarifier ceci: "Clarifier la spécification vague de object.__await__" . Il est actuellement en cours de révision.
Réponses
La langue ne se soucie pas de l'itérateur que vous retournez. L'erreur provient d'une bibliothèque , asyncio, qui a des idées spécifiques sur le type de valeurs qui doivent être produites par l'itérateur. Asyncio nécessite __await__de produire des futurs asyncio (y compris leurs sous-types tels que les tâches) ou None. D'autres bibliothèques, comme curio et trio, s'attendront à différents types de valeurs. Les bibliothèques Async ne documentent généralement pas leurs attentes __await__car elles le considèrent comme un détail d'implémentation.
En ce qui concerne asyncio, vous êtes censé utiliser des constructions de plus haut niveau, telles que les futurs et les tâches, et les attendre, en plus des coroutines. Il est rarement nécessaire de mettre en œuvre __await__manuellement, et même alors vous devriez l' utiliser pour déléguer les signaux d' une autre awaitable. Une écriture __await__qui produit et produit sa propre valeur nécessite qu'elle soit couplée à la boucle d'événements et qu'elle connaisse ses éléments internes.
Vous pouvez penser __await__à un outil pour écrire une bibliothèque similaire à asyncio. Si vous êtes l'auteur d'une telle bibliothèque, la spécification actuelle est suffisante car vous pouvez donner ce que vous voulez de l'itérateur, seul le code de votre boucle d'événements observera les valeurs fournies. Si vous n'êtes pas dans cette position, vous n'avez probablement aucune entreprise à mettre en œuvre __await__.
Les tâches ne peuvent attendre que d'autres tâches / futurs. À partir du code source CPython :
/* Check if `result` is FutureObj or TaskObj (and not a subclass) */
/* ... */
/* Check if `result` is None */
/* ... error */
/* Check if `result` is a Future-compatible object */
/* ... */
/* Check if `result` is a generator */
/* ... */
/* The `result` is none of the above */
o = task_set_error_soon(
task, PyExc_RuntimeError, "Task got bad yield: %R", result);
Py_DECREF(result);
return o;
Edit: Si je comprends bien, cette restriction n'est imposée qu'aux tâches, et les futurs normaux peuvent attendre tout itérable renvoyé __await__, bien que le point soit probablement que l'itérable renvoyé cède à la boucle d'événements, puis finit par renvoyer un résultat.