2012-02-09 4 views
14

letto una domanda su stack overflow qualche tempo indietro con la seguente sintassiconfondersi con lambda e di lista

In [1]: [lambda: x for x in range(5)][0]() 
Out[1]: 4 
In [2]: [lambda: x for x in range(5)][2]() 
Out[2]: 4 

Ma io sto avendo un momento difficile capire perché esattamente l'output di questo si presenta come 4, mia comprensione è che dà sempre l'ultimo valore della lista come uscita,

In [4]: [lambda: x for x in [1,5,7,3]][0]() 
Out[4]: 3 

ma ancora non convinto come fa questa sintassi finisce con l'ultimo valore.

sarebbe molto contento se posso ottenere una spiegazione adeguata per questa sintassi

+0

Pensi che potresti trovare quella domanda che hai menzionato? Questo è interessante, mi piacerebbe capirlo anche :) – kevintodisco

+2

'In [84]: [z() per z in (lambda: x per x nel range (5))]' 'Out [84]: [0, 1, 2, 3, 4] ' ' In [85]: [z() per z in [lambda: x per x nell'intervallo (5)]] ' ' Out [85]: [4 , 4, 4, 4, 4] ' se viene utilizzato il generatore anziché la comprensione delle liste, anche l'output sarà diverso! – shahjapan

+1

@ktodisco: Sono abbastanza sicuro che è la mia domanda: http://stackoverflow.com/questions/9189702/python-closures-and-cells-closed-over-values ​​ –

risposta

15

Non si tratta in realtà di una lista di comprensione o di un lambda. Riguarda le regole di scoping in Python. Riscriviamo la lista di comprensione in un ciclo equivalente:

funcs = [] 
for x in range(5): 
    def f(): return x 
    funcs.append(f) 
funcs[0]() # returns 4 

Qui, possiamo vedere che abbiamo successivamente costruire funzioni e memorizzarli in una lista. Quando viene chiamata una di queste funzioni, tutto ciò che accade è il valore di x viene cercato e restituito. Ma x è una variabile il cui valore cambia, quindi il valore finale di 4 è ciò che viene sempre restituito. Si potrebbe anche modificare il valore della x dopo il ciclo, per esempio,

x = 32 
funcs[2]() # returns 32 

per ottenere il comportamento che vi aspettavate, Python avrebbe bisogno di portata le for contenuto come un blocco; non è così. Di solito, questo non è un problema, ed è abbastanza facile da aggirare.

4

Per un LC con n iterazioni, x viene assegnato elementi 0 a n-1 della sequenza sorgente. All'ultima iterazione, x viene assegnato l'ultimo elemento. Il punto da notare è che è sempre lo stesso x, e chiamando la funzione alla fine restituisce qualsiasi cosa x tenuta per ultima.

+2

puoi per favore spezzare il codice in più passaggi in modo che io possa capirlo in un modo migliore :) – avasal

+0

@avasal Il lambda racchiude il * nome * 'x', non il valore di' x'. Al termine dell'LC, hai un elenco di lambda che restituiscono il valore associato al nome 'x' - il valore più recente. – Daenyth

+0

se l'LC completa il primo e l'ultimo valore viene restituito a lambda, sono confuso perché ci sono 5 funzioni lambda In [6]: [lambda: x per x nel range (5)] Out [6]: [ a 0x16b7ed8>, a 0x16b7e60>, a 0x16c10c8>, a 0x16c1140>, a 0x16c11b8>] – avasal

1

Questo sarà risolvere il problema:

[(lambda(i): lambda: i)(x) for x in range(5)][2]() 

Il problema è che non si sta catturando il valore di x su ogni iterazione della lista di comprensione, si sta catturando la variabile ogni volta attraverso .

+1

non ho bisogno di risolvere questo ho bisogno di capire cosa sta succedendo lì :) – avasal

+1

Penso che la domanda di OP sia capire come Python interpreta quando non cattura il valore di 'x'. :) – shahjapan

3

Permettetemi di rompere il codice per la mia comprensione

In [39]: [lambda: x for x in [1,5,7,3]] 
Out[39]: 
[<function <lambda> at 0x2cd1320>, 
<function <lambda> at 0x2cd12a8>, 
<function <lambda> at 0x2cd10c8>, 
<function <lambda> at 0x2cd1050>] 

sopra dà la lista delle funzioni

In [40]: [lambda: x for x in [1,5,7,3]][1] 
Out[40]: <function <lambda> at 0x2cd1488> 

L'indice 1 fornisce 1 funzione dall'elenco delle funzioni.

Ora questa funzione si applica a x, che ha sempre l'ultimo valore di lista. Quello y dà sempre l'ultimo valore come risultato.

come nel seguente codice.

In [41]: [lambda: 2][0]() 
Out[41]: 2 


In [42]: alist = [1,5,7,3,4,5,6,7] 

x per x in [1,5,7,3] equivale alla funzione di sotto f (x). e
lambda: x per x in [1,5,7,3] è equivalente a lambda: 3

In [43]: def f(x): 
    ....:  for x in alist: 
    ....:   pass 
    ....:  return x 
In [44]: print f(alist) 
7 
0

Come altri hanno detto, hai incontrato il comune problema loop-chiusura; cioè il problema che una chiusura cattura le variabili per riferimento. In questo caso stai acquisendo una variabile il cui valore cambia nel tempo (come con tutte le variabili di ciclo), quindi ha un valore diverso quando lo esegui rispetto a quando lo hai creato.

Per ottenere ciò che si desidera, è necessario acquisire il valore della variabile nel momento in cui viene creata la lambda. sblom ha suggerito la soluzione di avvolgendolo in una funzione immediatamente, eseguita:

[(lambda(i): lambda: i)(x) for x in range(5)][2]() 

Un'altra soluzione che spesso si vedrà in Python utilizza gli argomenti di default, che sarà anche di valutare il valore al momento della creazione:

[lambda i=x: i for x in range(5)][2]() 

(di solito è scritto lambda x=x: x, ma ho rinominato la variabile per chiarezza)