2012-11-13 12 views
24

Ich versuche, Closures zu verwenden, um eine Variable aus einer Funktionssignatur zu eliminieren (die Anwendung besteht darin, alle Funktionen zum Verbinden von Qt-Signalen für eine Schnittstelle zu machen, um eine größere Anzahl von Parametern an das Wörterbuch zu steuern speichert die Werte).Python Lambda Schließung Scoping

Ich verstehe nicht, warum der Fall der Verwendung der lambda nicht in einer anderen Funktion verpackt den Nachnamen für alle Fälle zurückgibt.

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 

Antwort

44

Der Grund ist, dass Verschlüsse (lambdas oder anders) über Namen schließen, nicht Werte. Wenn Sie lambda x: test_fun(n, x) definieren, wird das n nicht ausgewertet, da es sich innerhalb der Funktion befindet. Es wird ausgewertet, wenn die Funktion aufgerufen wird. Zu diesem Zeitpunkt ist der Wert der letzte Wert der Schleife.

Sie sagen am Anfang, dass Sie "Closures verwenden möchten, um eine Variable aus einer Funktionssignatur zu entfernen", aber das funktioniert nicht wirklich so. (Siehe jedoch unten für einen Weg, der Sie zufriedenstellt, je nachdem, was Sie mit "eliminieren" meinen.) Variablen innerhalb des Funktionskörpers werden nicht ausgewertet, wenn die Funktion definiert ist. Um die Funktion dazu zu bringen, einen "Schnappschuss" der Variablen zu machen, wie sie zum Zeitpunkt der Funktionsdefinition existiert, müssen Sie die Variable als Argument übergeben. Die übliche Vorgehensweise besteht darin, der Funktion ein Argument zu übergeben, dessen Standardwert die Variable aus dem äußeren Bereich ist. Betrachten Sie den Unterschied zwischen diesen beiden Beispiele:

>>> 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 

In dem zweiten Beispiel n als Argument für die Funktion „Sperren in“ dem aktuellen Wert von n für diese Funktion übergeben. Sie müssen so etwas tun, wenn Sie den Wert auf diese Weise sperren möchten. (Wenn dies nicht funktioniert, funktionieren Dinge wie globale Variablen überhaupt nicht. Es ist wichtig, dass freie Variablen zum Zeitpunkt der Verwendung nachgeschlagen werden.)

Beachten Sie, dass nichts an diesem Verhalten für Lambdas spezifisch ist . Die gleichen Bereichsregeln gelten, wenn Sie def verwenden, um eine Funktion zu definieren, die auf Variablen aus dem umschließenden Bereich verweist.

Wenn Sie wirklich wollen, können Sie Ihre zurück Funktion Hinzufügen des zusätzlichen Argument vermeiden, sondern zu tun, so müssen Sie diese Funktion noch in einer anderen Funktion wickeln, etwa so:

>>> 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 

Hier ist die innere Lambda sucht immer noch den Wert n, wenn es aufgerufen wird. Aber die n, auf die es sich bezieht, ist keine globale Variable mehr, sondern eine lokale Variable innerhalb der umschließenden Funktion makeFunc. Ein neuer Wert dieser lokalen Variablen wird jedes Mal erstellt, wenn makeFunc aufgerufen wird, und das zurückgegebene Lambda erstellt eine Closure, die den lokalen Variablenwert "speichert", der für diesen Aufruf von makeFunc in Kraft war. Daher hat jede in der Schleife erzeugte Funktion ihre eigene "private" Variable mit dem Namen x. (Für diesen einfachen Fall kann dies auch mit einem Lambda für die äußere Funktion gemacht werden --- stuff = [(lambda n: lambda x: x+n)(n) for n in [1, 2, 3]] --- aber das ist weniger lesbar.)

Beachten Sie, dass Sie noch Ihre n als Argument übergeben müssen, ist es Nur indem Sie es auf diese Weise tun, übergeben Sie es nicht als Argument an die gleiche Funktion, die in die stuff Liste geht; Stattdessen übergeben Sie es als Argument an eine Hilfsfunktion, die die Funktion erstellt, die Sie in stuff einfügen möchten.Der Vorteil der Verwendung dieses Zwei-Funktions-Ansatzes besteht darin, dass die zurückgegebene Funktion "sauber" ist und nicht das zusätzliche Argument hat; Dies könnte nützlich sein, wenn Sie Funktionen umschließen, die viele Argumente akzeptieren. In diesem Fall kann es verwirrend sein, sich daran zu erinnern, wo das n Argument in der Liste war. Der Nachteil ist, dass es auf diese Art und Weise schwieriger ist, die Funktionen zu erstellen, da Sie eine andere umschließende Funktion benötigen.

Das Ergebnis ist, dass es einen Kompromiss gibt: Sie können den Funktionserstellungsprozess einfacher machen (dh keine Notwendigkeit für zwei verschachtelte Funktionen), aber dann müssen Sie die resultierende Funktion etwas komplizierter machen (dh es hat Dieses zusätzliche Argument). Oder Sie können die Funktion einfacher machen (d. H., Es hat kein n= Argument n), aber dann müssen Sie den Funktionserstellungsprozess komplizierter machen (d. H. Sie benötigen zwei verschachtelte Funktionen, um den Mechanismus zu implementieren).

+0

Dies ist eine weitaus bessere Erklärung für dieses Python-Verhalten als alles, was in [diese populäreren Antworten auf eine ähnliche Frage] geschrieben wurde (https://stackoverflow.com/questions/2295290/what-do-lambda-function-closures -capture/23557126) –