7

volevo avere un elenco di lambda che agiscono come una sorta di cache per alcuni calcoli complessi e notato questo:lambda elenco all'interno comprehensions

>>> [j() for j in [lambda:i for i in range(10)]] 
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9] 

Sebbene

>>> list([lambda:i for i in range(10)]) 
[<function <lambda> at 0xb6f9d1ec>, <function <lambda> at 0xb6f9d22c>, <function <lambda> at 0xb6f9d26c>, <function <lambda> at 0xb6f9d2ac>, <function <lambda> at 0xb6f9d2ec>, <function <lambda> at 0xb6f9d32c>, <function <lambda> at 0xb6f9d36c>, <function <lambda> at 0xb6f9d3ac>, <function <lambda> at 0xb6f9d3ec>, <function <lambda> at 0xb6f9d42c>] 

senso che le lambda sono funzioni uniche ma in qualche modo condividono tutti lo stesso valore di indice.

È un bug o una funzionalità? Come evito questo problema? Non è limitato a list comprehension ...

>>> funcs = [] 
... for i in range(10): 
...  funcs.append(lambda:i) 
... [j() for j in funcs] 
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9] 

risposta

13

lambda restituisce il valore di i nel momento in cui viene chiamato. Poiché si chiama lambda al termine del ciclo, il valore di i sarà sempre 9.

È possibile creare una variabile locale i nel lambda per mantenere il valore nel momento in cui lambda è stata definita:

>>> [j() for j in [lambda i=i:i for i in range(10)]] 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Un'altra soluzione è quella di creare una funzione che restituisce il lambda:

def create_lambda(i): 
    return lambda:i 
>>> [j() for j in [create_lambda(i) for i in range(10)]] 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Questo funziona perché esiste una chiusura diversa (con un valore diverso di i) creata per ciascuna chiamata di create_lambda.

+0

Il primo approccio non funziona così bene quando vuoi che 'lambda' supporti' * args'. Il secondo approccio funzionerà lì, ma è ... più lavoro :) –

+2

Peccato che non possa dare il segno verde a entrambe le risposte. Ho scelto questo perché in realtà ha dato il codice corretto per tagliare e incollare come se avessi cinque anni e mi piace quell'approccio alle risposte su SO. – ubershmekel

+0

Per quelli di noi meno bravi in ​​Python, c'è qualche possibilità di usare una variabile diversa da 'i' per una delle istanze nel primo approccio? – Chowlett

0

non sono sicuro se si tratta di un bug o di una funzione, ma quello che sta succedendo è che lambda:i non valutare i prima di formare la funzione lambda. Quindi, letteralmente è solo una funzione che valuta qualunque sia il valore corrente di i. Ecco un altro esempio di come ciò accade.

>>> i=5 
>>> x=lambda:i 
>>> x() 
5 
>>> i=6 
>>> x() 
6 

Così, ovviamente, quello che sta succedendo è la stessa cosa, tranne che mi sta per 9 nei tuoi esempi mentre viene assegnato attraverso la gamma da 0 a 9 in questo ordine.

Non penso che ci sia davvero un buon modo per evitarlo. Le funzioni Lambda in Python sono piuttosto limitate. Non è davvero un linguaggio funzionale nel cuore.

7

Quello che vedete qui è l'effetto di closures. Il lambda sta catturando lo stato dal programma da utilizzare in seguito. Quindi, mentre ogni lambda è un oggetto unico, lo stato non è necessariamente univoco.

L'effettivo 'gotchya' qui è che la variabile i viene acquisita, non il valore che i rappresenta in quel momento. Possiamo illustrare questo con un esempio molto più semplice:

>>> y = 3 
>>> f = lambda: y 
>>> f() 
3 
>>> y = 4 
>>> f() 
4 

La lambda tiene al riferimento alla variabile, e valuta che variabile quando si esegue il lambda.

Per risolvere questo problema, è possibile assegnare a una variabile locale all'interno del lambda:

>>> f = lambda y=y:y 
>>> f() 
4 
>>> y = 6 
>>> f() 
4 

Infine, nel caso di un ciclo, l'indice del ciclo è solo 'dichiarata' una volta. Pertanto, qualsiasi riferimento alla variabile di ciclo all'interno del ciclo persisterà dopo l'iterazione successiva. Questo include la variabile nella comprensione delle liste.

+0

Bene, qualsiasi funzione lambda è una chiusura. Non penso che il manifesto fosse confuso su cosa fosse una chiusura. La parte inaspettata è che sta usando un riferimento di variabile in questo caso piuttosto che il valore. La maggior parte delle lingue che usano chiusure sostituiscono il valore in questa circostanza a meno che non si utilizzi un tipo di riferimento. –

+4

@KeithIrwin, la maggior parte delle lingue che consentono alle chiusure di soffrire di questo esatto 'gotchya'. Javascript e C# sono due esempi degni di nota. Inoltre, l'OP non ha mai menzionato la parola "chiusura", quindi ho pensato di collegarmi a materiale che lo spiegasse se non fosse consapevole. –

+0

Si presenta solo in lingue che non distinguono chiaramente tra valori e riferimenti. Ad esempio, si presenta nella famiglia dei linguaggi funzionali Lisp, ma non nei linguaggi più tipizzati come la famiglia ML o Haskell o Scala. Se avesse lavorato in una lingua come F #, Ocaml, Haskell o Scala, avrebbe ragionevolmente aspettato il comportamento esattamente opposto. Presumo che abbia almeno una certa esperienza con i linguaggi funzionali perché ha etichettato il post come relativo alla programmazione funzionale. –

1

Il problema è che non stai acquisendo il valore di i su ogni iterazione della comprensione delle liste, stai catturando la variabile ogni volta.

Il problema è che una chiusura acquisisce 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.

0

Non sono sicuro di cosa si sta tentando di fare con la funzione lambda. Non ci vuole un argomento ...

Penso che Python stia facendo un generatore simile a (i for i in range(10)) e quindi iterando quello. Dopo che conta 10 volte, il valore finale di i è 9, e quindi restituisce la funzione lambda.

Volevi creare una sorta di oggetto che avrebbe dato i numeri da [0,9]? Perché se vuoi farlo, usa semplicemente xrange(10), che restituisce un iteratore che restituisce i numeri [0,9].

def f(x): 
    return x 

lst = [f(x) for x in xrange(10)] 
print(lst == range(10)) # prints True 

Nota: Penso che sia una pessima idea per cercare di nominare una funzione j(). Python utilizza j per indicare la parte immaginaria di un numero complesso e penso che una funzione denominata j potrebbe confondere il parser. Ho ricevuto alcuni strani messaggi di errore cercando di eseguire il tuo codice.

Problemi correlati