2012-11-13 13 views
24

Sto cercando di utilizzare le chiusure per eliminare una variabile da una firma di funzione (l'applicazione è rendere la scrittura di tutte le funzioni necessarie per connettere i segnali Qt per un'interfaccia per controllare un numero elevato di parametri per il dizionario che memorizza i valori).Python lambda closure scoping

Non capisco perché il caso dell'utilizzo di lambda non inserito in un'altra funzione restituisca il cognome per tutti i casi.

names = ['a', 'b', 'c'] 

def test_fun(name, x): 
    print(name, x) 

def gen_clousure(name): 
    return lambda x: test_fun(name, x) 

funcs1 = [gen_clousure(n) for n in names] 
funcs2 = [lambda x: test_fun(n, x) for n in names] 

# this is what I want 
In [88]: for f in funcs1: 
    ....:  f(1) 
a 1 
b 1 
c 1 

# I do not understand why I get this 
In [89]: for f in funcs2: 
    ....:  f(1) 
c 1 
c 1 
c 1 

risposta

44

Il motivo è che le chiusure (lambda o altro) chiudono i nomi, non i valori. Quando si definisce lambda x: test_fun(n, x), n non viene valutato, poiché si trova all'interno della funzione. Viene valutato quando la funzione è denominata, momento in cui il valore presente è l'ultimo valore del ciclo.

Si dice all'inizio che si desidera "utilizzare chiusure per eliminare una variabile da una firma di funzione", ma in realtà non funziona in questo modo. (Vedi sotto, però, per un modo che potrebbe soddisfarti, a seconda di cosa intendi con "elimina"). Le variabili all'interno del corpo della funzione non saranno valutate quando la funzione è definita. Per fare in modo che la funzione acquisisca una "istantanea" della variabile così come esiste al momento della definizione della funzione, è necessario che risponda alla variabile come argomento come. Il solito modo per farlo è dare alla funzione un argomento il cui valore predefinito è la variabile dall'ambito esterno. Guardare la differenza tra questi due esempi:

>>> stuff = [lambda x: n+x for n in [1, 2, 3]] 
>>> for f in stuff: 
...  print f(1) 
4 
4 
4 
>>> stuff = [lambda x, n=n: n+x for n in [1, 2, 3]] 
>>> for f in stuff: 
...  print f(1) 
2 
3 
4 

Nel secondo esempio, passando n come argomento alla funzione "blocca in" il valore corrente di n per tale funzione. Devi fare qualcosa di simile se vuoi bloccare il valore in questo modo. (Se non funzionasse in questo modo, cose come le variabili globali non funzionerebbero affatto, è essenziale che le variabili libere vengano controllate al momento dell'uso.)

Si noti che nulla di questo comportamento è specifico per lambda . Le stesse regole di scoping sono effettive se si utilizza def per definire una funzione che fa riferimento a variabili dall'ambito di inclusione.

Se davvero si vuole, si può evitare di aggiungere l'argomento in più per la vostra funzione restituito, ma per farlo è necessario avvolgere quella funzione in un'altra funzione, in questo modo:

>>> def makeFunc(n): 
...  return lambda x: x+n 
>>> stuff = [makeFunc(n) for n in [1, 2, 3]] 
>>> for f in stuff: 
...  print f(1) 
2 
3 
4 

Qui, l'interno lambda cerca ancora il valore di n quando viene chiamato. Ma il numero n a cui fa riferimento non è più una variabile globale ma una variabile locale all'interno della funzione di inclusione makeFunc. Un nuovo valore di questa variabile locale viene creato ogni volta che viene chiamato makeFunc e il lambda restituito crea una chiusura che "salva" il valore della variabile locale che era in vigore per tale chiamata di makeFunc. Quindi ogni funzione creata nel ciclo ha la sua variabile "privata" chiamata x. (Per questo semplice caso, questo può anche essere fatto usando un lambda per la funzione esterna --- stuff = [(lambda n: lambda x: x+n)(n) for n in [1, 2, 3]] --- ma questo è meno leggibile.)

Nota che devi ancora passare il tuo n come argomento, è solo così, così facendo, non lo si passa come argomento alla stessa funzione che finisce nella lista stuff; invece lo si passa come argomento a una funzione di supporto che crea la funzione che si desidera inserire in stuff.Il vantaggio dell'utilizzo di questo approccio a due funzioni è che la funzione restituita è "pulita" e non ha l'argomento extra; questo potrebbe essere utile se stavi avvolgendo funzioni che accettano molti argomenti, nel qual caso potrebbe diventare difficile ricordare dove l'argomento n era presente nell'elenco. Lo svantaggio è che, facendo in questo modo, il processo di creazione delle funzioni è più complicato, dal momento che è necessaria un'altra funzione di inclusione.

Il risultato è che esiste un compromesso: è possibile semplificare il processo di creazione delle funzioni (ovvero, non è necessario disporre di due funzioni nidificate), ma in tal caso è necessario rendere la funzione risultante un po 'più complicata (ad es. questo argomento extra n=n). Oppure puoi rendere la funzione più semplice (cioè non ha l'argomento n. n=), ma in tal caso devi rendere più complicato il processo di creazione di funzioni (ad esempio, hai bisogno di due funzioni annidate per implementare il meccanismo).

+0

Questa è una spiegazione molto migliore di questo comportamento Python rispetto a tutto ciò che è stato scritto in [queste risposte più popolari a una domanda simile] (https://stackoverflow.com/questions/2295290/what-do-lambda-function-closures -capture/23557126) –