Specifica precisa di __await__

Sep 18 2020

Il riferimento al linguaggio Python specifica object.__await__quanto segue:

object.__await__(self)

Deve restituire un iteratore. Dovrebbe essere utilizzato per implementare oggetti in attesa. Ad esempio, asyncio.Futureimplementa questo metodo per essere compatibile con l'espressione await.

Questo è tutto. Trovo questa specifica molto vaga e poco specifica (ironia della sorte). Ok, dovrebbe restituire un iteratore, ma può essere un iteratore arbitrario? Ovviamente no:

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

Presumo che il asynciociclo di eventi si aspetti che un tipo specifico di oggetto venga fornito dall'iteratore. Allora cosa dovrebbe produrre esattamente ? (E perché non è documentato?)


Modifica: per quanto posso vedere, questo non è documentato da nessuna parte. Ma Sto indagando per conto mio, e penso che la chiave per capire quali oggetti il asyncioaspetta che i suoi coroutine a cedere bugie in task_step_impla _asynciomodule.c.


Aggiornamento: ho fatto un PR al repository cpython con l'obiettivo di chiarire questo: "Chiarire la vaga specifica di object.__await__" . È attualmente in fase di revisione.

Risposte

7 user4815162342 Sep 19 2020 at 07:15

Alla lingua non importa quale iteratore restituisci. L'errore proviene da una libreria , asyncio, che ha idee specifiche sul tipo di valori che devono essere prodotti dall'iteratore. Asyncio richiede __await__di produrre futures asyncio (compresi i loro sottotipi come attività) o None. Altre biblioteche, come curio e trio, si aspettano diversi tipi di valori. Le librerie asincrone in generale non documentano le loro aspettative __await__perché lo considerano un dettaglio di implementazione.

Per quanto riguarda l'asincio, dovresti usare costrutti di livello superiore, come futuri e compiti, e attenderli, oltre alle coroutine. Raramente è necessario implementarlo __await__manualmente, e anche in questo caso dovresti usarlo per delegare i segnali di un'altra attesa. La scrittura __await__che produce e fornisce il proprio valore richiede che sia accoppiato con il ciclo di eventi e abbia conoscenza dei suoi interni.

Puoi pensare a __await__come uno strumento per scrivere una libreria simile ad asyncio. Se sei l'autore di una tale libreria, la specifica corrente è sufficiente perché puoi produrre tutto ciò che desideri dall'iteratore, solo il codice nel tuo ciclo di eventi osserverà i valori ottenuti. Se non sei in quella posizione, probabilmente non hai attività di implementazione __await__.

1 uanirudhx Sep 19 2020 at 02:16

Le attività possono attendere solo altre attività / futures. Dal codice sorgente 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;

Modifica: se capisco correttamente questa restrizione è imposta solo sulle attività e i futures normali possono attendere su qualsiasi iterabile restituito da __await__, anche se il punto è probabilmente che l'iterabile restituito restituisce il ciclo di eventi, quindi alla fine finisce per restituire un risultato.