2012-09-07 12 views
5

Sto giocando intorno con generatori e le espressioni del generatore e non sono del tutto sicura di aver capito come funzionano (some reference material):tentare di capire il rendimento come espressione

>>> a = (x for x in range(10)) 
>>> next(a) 
0 
>>> next(a) 
1 
>>> a.send(-1) 
2 
>>> next(a) 
3 

così sembra generator.send era ignorato. Che abbia un senso (credo) perché non c'è esplicito yield espressione di catturare le informazioni inviate ...

Tuttavia,

>>> a = ((yield x) for x in range(10)) 
>>> next(a) 
0 
>>> print next(a) 
None 
>>> print next(a) 
1 
>>> print next(a) 
None 
>>> a.send(-1) #this send is ignored, Why? ... there's a yield to catch it... 
2 
>>> print next(a) 
None 
>>> print next(a) 
3 
>>> a.send(-1) #this send isn't ignored 
-1 

Capisco che questo è piuttosto lontano là fuori, e io (al momento) può Pensiamo a un caso d'uso per questo (quindi non chiedete;)

Per lo più sto solo esplorando per cercare di capire come funzionano questi vari metodi di generatore (e come le espressioni generatrici funzionano in generale). Perché il mio secondo esempio si alterna tra la resa di un valore ragionevole e None? Inoltre, qualcuno può spiegare perché uno dei miei generator.send è stato ignorato mentre l'altro no?

+1

Controlla se questo collegamento può aiutarti ... http://stackoverflow.com/questions/231767/the-python-yield-keyword-explained –

risposta

3

La confusione qui è che l'espressione del generatore sta facendo uno yield nascosto. Qui è in forma di funzione:

def foo(): 
    for x in range(10): 
     yield (yield x) 

Quando si esegue un .send(), ciò che accade è l'interno yield x Viene eseguito, che produce x. Quindi l'espressione valuta il valore di .send e il rendimento successivo produce quello. Qui è in forma più chiara:

def foo(): 
    for x in range(10): 
     sent_value = (yield x) 
     yield sent_value 

Così l'uscita è molto prevedibile:

>>> a = foo() 
#start it off 
>>> a.next() 
0 
#execution has now paused at "sent_value = ?" 
#now we fill in the "?". whatever we send here will be immediately yielded. 
>>> a.send("yieldnow") 
'yieldnow' 
#execution is now paused at the 'yield sent_value' expression 
#as this is not assigned to anything, whatever is sent now will be lost 
>>> a.send("this is lost") 
1 
#now we're back where we were at the 'yieldnow' point of the code 
>>> a.send("yieldnow") 
'yieldnow' 
#etc, the loop continues 
>>> a.send("this is lost") 
2 
>>> a.send("yieldnow") 
'yieldnow' 
>>> a.send("this is lost") 
3 
>>> a.send("yieldnow") 
'yieldnow' 

EDIT: Esempio dell'uso. Di gran lunga il più bello che ho visto finora è la funzione inlineCallbacks di twisted. See here per un articolo che lo spiega. Il nocciolo di esso è che ti permette di fornire le funzioni da eseguire nei thread, e una volta che le funzioni sono terminate, twisted rimanda il risultato della funzione nel tuo codice. In questo modo è possibile scrivere codice che si basa pesantemente sui thread in modo molto lineare e intuitivo, invece di dover scrivere tonnellate di piccole funzioni dappertutto.

Vedere PEP 342 per ulteriori informazioni sulla base logica del fatto che .send funziona con potenziali casi d'uso (l'esempio perverso che ho fornito è un esempio di vantaggio per l'I/O asincrono di questo cambiamento offerto).

+0

Grazie, questo è stato molto utile. – mgilson

+0

Hmmm ... Suppongo che potresti usare questo per unire 2 liste '['a', 'c', 'e']' e '['b', 'd', 'f']' in '[' a ',' b ',' c ',' d ',' e ',' f '] '... – mgilson

+0

@mgilson: puoi anche usare' + 'per quello. aggiornerò presto la mia risposta su un caso d'uso valido – Claudiu

2

Questo generatore si traduce in:

for i in xrange(10): 
    x = (yield i) 
    yield x 

Risultato della seconda chiamata a send()/next() vengono ignorati, perché non fai niente con il risultato di uno dei rendimenti.

2

Ti stai confondendo un po 'perché in realtà stai generando da due fonti: l'espressione del generatore (... for x in range(10)) è un generatore, ma tu crei un'altra fonte con yield. Puoi vedere che se fai list(a) avrai [0, None, 1, None, 2, None, 3, None, 4, None, 5, None, 6, None, 7, None, 8, None, 9, None].

Il codice è equivalente alla presente:

>>> def gen(): 
...  for x in range(10): 
...   yield (yield x) 

Solo il rendimento interno ("yield x") viene "usato" nel generatore --- viene utilizzato come valore della resa esterno. Quindi questo generatore itera avanti e indietro tra i valori di rendimento dell'intervallo e restituisce ciò che è "inviato" a quei rendimenti. Se invii qualcosa al rendimento interno, lo recuperi, ma se ti capita di inviare un'iterazione pari, l'invio viene inviato al rendimento esterno e viene ignorato.

+0

Non ho creato 2 generatori - Ho creato 1 generatore con 2 dichiarazioni di rendimento ;-) (apparentemente) – mgilson

+0

Hai ragione, un modo migliore per dire che potresti generare da due fonti. – BrenBarn

+0

Mi ha semplicemente sconvolto quando ho capito che 'yield' era un'espressione (non un'affermazione). Così ho iniziato a giocare in giro per cercare di capire cosa avrei potuto * fare effettivamente con quella conoscenza * ... – mgilson

-1

In effetti, il metodo send è concepito per funzionare con un oggetto generatore che è il risultato di una co-routine che è stata scritta esplicitamente. È difficile avere un significato in un'espressione di generatore, anche se funziona.

- EDIT - avevo già scritto questo, ma è incorrecct, come rendimento all'interno espressioni generatore sono prevedibili attraverso le implementazioni - anche se non menzionato in alcun PEP.

generator expressions are not meant to have the yield keyword - I am not shure the behavior is even defined in this case. We could think a little and get to what is happening on your expression, to meet from where those "None"s are coming from. However, assume that as a side effect of how the yield is implemented in Python (and probably it is even implementation dependent), not as something that should be so.

La corretta per un'espressione generatore, in maniera semplificata è:

(<expr> for <variable> in <sequence> [if <expr>]) 

così, <expr> viene valutata per ogni valore nella <sequence: - non solo è yield uneeded, come non si dovrebbe usalo

Sia yield ei metodi send sono destinati ad essere utilizzati in piena collaborazione routine, qualcosa di simile:

def doubler(): 
    value = 0 
    while value < 100: 
     value = 2 * (yield value) 

E lo si può utilizzare come:

>>> a = doubler() 
>>> # Next have to be called once, so the code will run up to the first "yield" 
... 
>>> a.next() 
0 
>>> a.send(10) 
20 
>>> a.send(20) 
40 
>>> a.send(23) 
46 
>>> a.send(51) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
StopIteration 
>>> 
+0

Giù l'elettore si preoccupa di commentare? – jsbueno

+1

aye. "le espressioni generatrici non sono pensate per avere la parola chiave yield - non sono sicuro che il comportamento sia definito in questo caso". è perfettamente definito. vedi [questo pastebin] (http://pastebin.com/Yq6XeTUg). è solo un po 'strano, ma ha senso. – Claudiu

+0

il None non proviene da una cosa dipendente dall'implementazione, solo che stava cedendo Nones (chiamando .next() che ha inviato None al rendimento) – Claudiu

0

Il generatore che hai scritto è equivalente al più dettagliato:

def testing(): 
    for x in range(10): 
      x = (yield x) 
      yield x 

Come puoi vedere e, il secondo yield, che è implicito nell'espressione del generatore, non salva il valore in cui lo si passa, quindi, a seconda di dove è bloccata l'esecuzione del generatore, il send può o non può funzionare.