2016-07-14 35 views
52

Sto provando a ripetere la funzione lambda su un elenco come in test.py, e voglio ottenere il risultato della chiamata del lambda, non dell'oggetto funzione stesso. Tuttavia, il seguente output mi ha davvero confuso.Lambdas da una list comprehension restituisce un lambda quando viene chiamato

------test.py--------- 
#!/bin/env python 
#coding: utf-8 

a = [lambda: i for i in range(5)] 
for i in a: 
    print i() 

--------output--------- 
<function <lambda> at 0x7f489e542e60> 
<function <lambda> at 0x7f489e542ed8> 
<function <lambda> at 0x7f489e542f50> 
<function <lambda> at 0x7f489e54a050> 
<function <lambda> at 0x7f489e54a0c8> 

ho modificato il nome della variabile quando il risultato di stampa chiamata a t come seguendo, e tutto va bene. Mi chiedo di cosa si tratta. ?

--------test.py(update)-------- 
a = [lambda: i for i in range(5)] 
for t in a: 
    print t() 

-----------output------------- 
4 
4 
4 
4 
4 
+1

Scusate per la chiusura, ho letto male la domanda. –

+1

Whoops, mi dispiace per la segnalazione. Ho letto male anche io! –

+3

Ho cambiato il titolo perché era * veramente * facile sfogliare la tua domanda e penso che tu avessi un semplice problema di binding tardivo invece di un problema relativo al riutilizzo del nome della variabile. (Ciò è evidenziato nelle chiusure e risposte errate). Il nuovo titolo richiama l'attenzione sul vero problema che ti preoccupa. Se vuoi riformularlo da quello che ho realizzato, assicurati di mantenere questo aspetto. – jpmc26

risposta

47

in Python 2 di lista 'fughe' le variabili da portata esterna:

>>> [i for i in xrange(3)] 
[0, 1, 2] 
>>> i 
2 

notare che il comportamento è diverso su Python 3:

>>> [i for i in range(3)] 
[0, 1, 2] 
>>> i 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
NameError: name 'i' is not defined 

Quando si definisce lambda è destinato alla variabile i, non il suo valore corrente come mostra il tuo secondo esempio. Ora, quando si assegna al nuovo valore i lambda restituirà tutto ciò che è il valore corrente:

>>> a = [lambda: i for i in range(5)] 
>>> a[0]() 
4 
>>> i = 'foobar' 
>>> a[0]() 
'foobar' 

Poiché il valore della i all'interno del loop è la lambda per sé si otterrà come valore di ritorno:

>>> i = a[0] 
>>> i() 
<function <lambda> at 0x01D689F0> 
>>> i()()()() 
<function <lambda> at 0x01D689F0> 

UPDATE: Esempio su Python 2.7:

Python 2.7.6 (default, Jun 22 2015, 17:58:13) 
[GCC 4.8.2] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> a = [lambda: i for i in range(5)] 
>>> for i in a: 
...  print i() 
... 
<function <lambda> at 0x7f1eae7f15f0> 
<function <lambda> at 0x7f1eae7f1668> 
<function <lambda> at 0x7f1eae7f16e0> 
<function <lambda> at 0x7f1eae7f1758> 
<function <lambda> at 0x7f1eae7f17d0> 

Same su Python 3.4:

Python 3.4.3 (default, Oct 14 2015, 20:28:29) 
[GCC 4.8.4] on linux 
Type "help", "copyright", "credits" or "license" for more information. 
>>> a = [lambda: i for i in range(5)] 
>>> for i in a: 
...  print(i()) 
... 
4 
4 
4 
4 
4 

Per i dettagli relativi alla variazione per quanto riguarda la portata variabile di lista vedere Guido blogpost from 2010.

Abbiamo anche apportato un altro cambiamento in Python 3, per migliorare l'equivalenza tra le list comprehensions e le espressioni del generatore. In Python 2, la lista di comprensione "perde" la variabile di controllo del ciclo nel perimetro circostante:

x = 'before' 
a = [x for x in 1, 2, 3] 
print x # this prints '3', not 'before' 

Tuttavia, in Python 3, abbiamo deciso di risolvere il "piccolo sporco segreto" della list comprehension da utilizzando la stessa strategia di implementazione delle espressioni del generatore. Quindi, in Python 3, l'esempio sopra (dopo la modifica per usare print (x) :-) stamperà 'before', dimostrando che la 'x' nella comprensione delle liste temporaneamente ombreggia ma non sovrascrive la 'x' nell'ambiente circostante scopo.

+2

L'ambito non comprensibile delle list comprehensions in python 2 non influisce sul comportamento nell'OP. –

+2

@ juanpa.arrivillaga Hai provato a eseguire il codice in questione con entrambe le versioni? Almeno ottengo risultati diversi con Python 2.7 e 3.4. – niemmi

+0

Whoops! Ho completamente letto male questa domanda! Hai completamente ragione! Questo è un comportamento sorprendente per me ... –

19

chiusure in Python sono late-binding, che significa che ogni funzione lambda nell'elenco valuterà solo la variabile i quando viene richiamato, e non quando definito. Ecco perché tutte le funzioni restituiscono lo stesso valore, vale a dire l'ultimo valore di ì (che è 4).

Per evitare questo, una tecnica è quella di associare il valore di i ad un locale di parametro denominato:

>>> a = [lambda i=i: i for i in range(5)] 
>>> for t in a: 
... print t() 
... 
0 
1 
2 
3 
4 

Un'altra opzione è quella di creare una partial function e legare il valore corrente di i come parametro:

>>> from functools import partial 
>>> a = [partial(lambda x: x, i) for i in range(5)] 
>>> for t in a: 
... print t() 
... 
0 
1 
2 
3 
4 

Modifica: Siamo spiacenti, riconosciuto male la questione inizialmente, dal momento che questo tipo di domande sono così spesso sull'associazione tardiva (grazie @soon f o il commento).

La seconda ragione del comportamento è la variabile di list comprehension che perde in Python2 come altri hanno già spiegato. Quando si utilizza i come variabile di iterazione nel ciclo for, ciascuna funzione stampa il valore corrente di i (per i motivi sopra indicati), che è semplicemente la funzione stessa. Quando si utilizza un nome diverso (ad esempio t), Funzioni di stampa l'ultimo valore di i come lo era nel ciclo di lista, che è 4.

+0

Bene, ma perché cambiare il nome della variabile nel ciclo 'for' cambia l'output? – soon

+0

@soon A quale variabile ti riferisci, il 't'? Ciò non influisce su nulla, è solo un nome temporaneo che diamo a ciascuna funzione quando si itera sulla lista. O ho frainteso la domanda? – plamut

+1

La mia interpretazione della domanda era "perché' per i in a' stampa lambdas, ma 'per k in a' stampa numeri?". Certo, questo è anche a causa del tardivo legame, ma questo potrebbe non essere chiaro per l'OP. +1 in ogni caso – soon

4

lambda: i è una funzione anonima senza alcun argomento che mi restituisce. Quindi stai generando un elenco di funzioni anonime, che puoi in seguito (nel secondo esempio) associare al nome t e invocare con (). Nota è possibile fare lo stesso con funzioni non anonimi:

>>> def f(): 
... return 42 
... 
>>> name = f 
>>> name 
<function f at 0x7fed4d70fb90> 
>>> name() 
42 

@plamut ha appena risposto dall'altra parte implicita della questione, in modo da non lo farò.

Problemi correlati