9

Sono nuovo in Python e nella programmazione funzionale. Sto usando la versione 2.7.6Prelevare un valore da una coroutine in Python, a.k.a. convertire la richiamata nel generatore

Sto usando il framework Tornado per fare richieste di rete asincrone. Da quello che ho imparato sulla programmazione funzionale, voglio che i miei dati scorrano attraverso il mio codice usando i generatori. Ho fatto la maggior parte di ciò di cui ho bisogno usando i generatori e trasformando i dati mentre passano attraverso le mie chiamate di funzione.

Alla fine del mio flusso, voglio fare una richiesta di REST per alcuni dati. Ho un ciclo for appena prima di inviare i miei dati a Tornado, avviare il pull e quindi inviare la richiesta http. L'oggetto http fornito da Tornado accetta una funzione di callback come opzione e restituisce sempre un futuro, che in realtà è un oggetto Tornado Future e non il futuro ufficiale di Python.

Il mio problema è che poiché ora sto usando i generatori per estrarre i miei dati attraverso il mio codice, non voglio più usare la funzione di callback. Il mio ragionamento per questo è che dopo aver recuperato i miei dati dalla richiamata, i miei dati vengono ora inseriti nel mio codice e non posso più utilizzare i generatori.

Il mio obiettivo è quello di creare un'interfaccia che appare in questo modo:

urls = (...generated urls...) 
responses = fetch(urls) 

Dove risposte è un generatore sopra gli URL completati.

Quello che ho tentato di fare, tra le molte cose, è convertire i risultati della richiamata in un generatore. Stavo pensando a qualcosa di simile, anche se sono ben lontano dall'implementarlo per altre questioni che spiegherò presto. Tuttavia, ho voluto il mio prendere la funzione di cercare qualcosa di simile:

def fetch(urls): 
    def url_generator(): 
     while True: 
      val = yield 
      yield val 

    @curry 
    def handler(gen, response): 
     gen.send(response) 

    gen = url_generator() 

    for u in urls: 
     http.fetch(u, callback=handler(gen)) 

    return gen 

ho semplificato il codice e la sintassi di concentrarsi sul problema, ma ho pensato che questo stava per funzionare bene. La mia strategia era quella di definire una coroutine/generatore a cui manderò le risposte, così come le ricevo.

Quello che ho più problemi è la coroutine/generatore. Anche se definisco un generatore nel modo sopra descritto ed eseguo il seguente, allora ottengo un ciclo infinito - questo è uno dei miei problemi principali.

def gen(): 
    while True: 
     val = yield 
     print 'val', val 
     yield val 
     print 'after', val 
     break 

g = gen() 
g.send(None) 
g.send(10) 

for e in g: 
    print e 

Questa stampa val 10 after 10 nel coroutine come previsto con la rottura, ma il ciclo for non viene mai il valore di 10. E non stampa nulla, mentre la rottura è lì. Se rimuovo la pausa, poi ho la ciclo infinito:

val None 
None 
after None 
None 
val None 
None 
after None 
None 
... 

Se rimuovo il ciclo for, allora la coroutine stamperà solo val 10 come si aspetta il secondo rendimento. Mi aspetto questo. Tuttavia, l'utilizzo non produce nulla.

Analogamente, se rimuovo il ciclo for e lo sostituisco con print next(g), ricevo un errore StopIteration, che presumo significa che ho chiamato su un generatore che non ha più valori.

Chiunque, io sono a una perdita completa mentre mi immergo in più profondità su Python. Immagino che questa situazione sia così comune in Python che qualcuno conosce un grande approccio. Ho cercato "converti il ​​callback in generatore" e così, ma non ho avuto molta fortuna.

In un'altra nota, potrei cedere ogni futuro dalla richiesta http, ma non ho avuto molta fortuna "in attesa" del rendimento per il futuro da completare.Ho letto molto su "yield from", ma sembra essere specifico per Python 3 e Tornado non sembra funzionare ancora su Python 3.

Grazie per la visualizzazione, e grazie per l'aiuto che puoi fornire.

+1

Impressionante domanda :). Ben scritto e ricercato. – Cyphase

+1

Grazie. Questa è la mia prima domanda :) – Joe

+0

Tornado funziona con python 3, è '@ curry' il tuo generatore? –

risposta

3

Tornado funziona alla grande su Python 3.

Il problema con il vostro codice semplificato di cui sopra è che questo non sta facendo quello che ci si aspetta:

val = yield 

Si prevede che il generatore per mettere in pausa lì (bloccando la for-loop) fino a quando alcune altre funzioni chiamano g.send(value), ma non è quello che succede. Invece, il codice si comporta come:

val = yield None 

Così il ciclo for riceve None valori velocemente come loro in grado di elaborare. Dopo aver ricevuto ciascuna None, chiama implicitamente lo g.next(), che corrisponde a g.send(None). Quindi, il codice è equivalente a questo:

def gen(): 
    while True: 
     val = yield None 
     print 'val', val 
     yield val 
     print 'after', val 

g = gen() 
g.send(None) 
g.send(10) 

while True: 
    try: 
     e = g.send(None) 
     print e 
    except StopIteration: 
     break 

La lettura di questa versione del codice, in cui i comportamenti impliciti vengono rese esplicite, spero che sia chiaro il motivo per cui è solo la generazione di None in un ciclo infinito.

Ciò che serve è un modo per una funzione di aggiungere elementi alla testa di una coda, mentre un'altra funzione blocca l'attesa di elementi e li estrae dalla coda della coda quando sono pronti. A partire dal Tornado 4.2 abbiamo esattamente questo:

http://www.tornadoweb.org/en/stable/queues.html

L'esempio ragnatela è vicino a quello che si vuole fare, io sono sicuro che si può adattare:

http://www.tornadoweb.org/en/stable/guide/queues.html

+0

Jesse, grazie mille per questa spiegazione. Lo contrassegnerò come corretto una volta ottenuto il corretto funzionamento del funzionamento e aggiornerò la mia domanda con una soluzione funzionante. È divertente, dopo aver eseguito questa chiamata, ho pianificato di mettere i miei risultati in MongoDB ... e stavo progettando di usare Motor! In realtà mi sono imbattuto nel tuo sito personale ieri durante la ricerca. Piccolo mondo :) Grazie ancora per tutto il tuo aiuto. – Joe

+0

Nessun problema, felice di poter aiutare un po '. Fammi sapere se c'è qualcos'altro che ti serve. –

Problemi correlati