7

Ich wollte eine Liste von Lambda-Ausdrücke haben, die als eine Art von Cache zu einigen schweren Berechnung handeln und bemerkte dies:Lambdas innerhalb Listenkomprehensionen

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

Obwohl

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

Bedeutung, dass die Lambda-Ausdrücke sind eindeutige Funktionen, aber sie haben alle den gleichen Indexwert.

Ist das ein Fehler oder eine Funktion? Wie vermeide ich dieses Problem? Es ist nicht zu Listenkomprehensionen begrenzt ...

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

Antwort

13

Die lambda gibt den Wert i zum Zeitpunkt des Aufrufs zurück. Da Sie die lambda nach Ablauf der Schleife aufrufen, wird der Wert i immer 9 sein.

Sie können ein lokales i Variable in dem Lambda schaffen den Wert an der Zeit zu halten die lambda definiert wurde:

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

Ein andere Lösung ist es, eine Funktion zu erstellen, die die lambda zurückzugibt:

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] 

Dies funktioniert, weil für jeden Aufruf von create_lambda ein anderer Abschluss (mit einem anderen Wert von i) erstellt wurde.

+0

Der erste Ansatz funktioniert nicht so gut, wenn Sie möchten, dass Ihr 'Lambda'' * args' 'unterstützt. Der zweite Ansatz wird dort funktionieren, ist aber ... mehr Arbeit :) –

+2

Schade, ich kann nicht beide Antworten grün markieren. Ich wählte dieses, weil es tatsächlich den richtigen Code zum Ausschneiden und Einfügen gab, wie ich fünf bin, und ich mag diese Annäherung an Antworten auf SO. – ubershmekel

+0

Für diejenigen von uns weniger gut in Python, jede Chance, Sie könnten eine andere Variable als "i" für eine der Instanzen in der ersten Annäherung verwenden? – Chowlett

0

Ich bin nicht sicher, ob es ein Bug oder ein Feature, aber was passiert ist, dass lambda:i nicht mich nicht bewerten, bevor die Lambda-Funktion zu bilden. Also ist es buchstäblich nur eine Funktion, die den aktuellen Wert von i auswertet. Hier ist ein weiteres Beispiel dafür.

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

Also, natürlich, was ist passiert die gleiche, außer dass ich bis 9 in Ihren Beispielen wird, wie es durch den Bereich von 0 bis 9 in dieser Reihenfolge zugewiesen werden wird.

Ich glaube nicht, dass es wirklich einen guten Weg gibt, um es zu vermeiden. Lambda-Funktionen in Python sind ziemlich begrenzt. Es ist nicht wirklich eine funktionale Sprache im Herzen.

7

Was Sie hier sehen, ist der Effekt von closures. Das Lambda erfasst den Status aus dem Programm, das später verwendet werden soll. Während also jedes Lambda ein einzigartiges Objekt ist, ist der Zustand nicht notwendigerweise einzigartig.

Das eigentliche "Gotchya" hier ist, dass die Variable i erfasst wird, nicht der Wert, den i zu diesem Zeitpunkt darstellt. Wir können dies illustrieren mit einem viel einfachen Beispiel:

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

Das Lambda hält auf die Variable mit der Referenz auf und wertet diese Variable, wenn Sie das Lambda ausführen.

Um dies zu umgehen, können Sie zu einer lokalen Variablen innerhalb der Lambda zuordnen:

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

schließlich im Falle einer Schleife ist die Schleifenvariable nur einmal ‚erklärt‘. Daher bleiben alle Verweise auf die Schleifenvariable innerhalb der Schleife über die nächste Iteration hinaus bestehen. Dies beinhaltet die Variable in List Comprehensions.

+0

Nun, jede Lambda-Funktion ist ein Verschluss. Ich glaube nicht, dass das Poster darüber verwirrt war, was eine Schließung war. Der unerwartete Teil ist, dass in diesem Fall eine Variablenreferenz verwendet wird und nicht der Wert. Die meisten Sprachen, die Schließungen verwenden, würden den Wert in diesem Fall ersetzen, wenn nicht ein Referenztyp verwendet würde. –

+4

@KeithIrwin, die meisten Sprachen, die Schließungen zulassen, leiden unter genau dieser "Gotchya". Javascript und C# sind zwei bemerkenswerte Beispiele. Außerdem erwähnte das OP nie das Wort Schließung, also dachte ich mir, ich würde auf Material verlinken, das es erklärt, wenn sie es nicht wussten. –

+0

Es erscheint nur in Sprachen, die nicht klar zwischen Werten und Referenzen unterscheiden. Zum Beispiel erscheint es in der Lisp-Familie der funktionalen Sprachen, aber nicht in den stärker typisierten Sprachen wie der ML-Familie oder Haskell oder Scala. Wenn er in einer Sprache wie F #, Ocaml, Haskell oder Scala gearbeitet hätte, würde er vernünftigerweise das genau entgegengesetzte Verhalten erwarten. Ich nehme an, dass er zumindest etwas Erfahrung mit funktionalen Sprachen hat, weil er den Beitrag als über funktionale Programmierung gekennzeichnet hat. –

1

Das Problem ist, dass Sie den Wert von i nicht bei jeder Iteration des Listenverständnisses erfassen, sondern die Variable jedes Mal erfassen.

Das Problem ist, dass eine Schließung Variablen durch Referenz erfasst. In diesem Fall erfassen Sie eine Variable, deren Wert sich im Laufe der Zeit ändert (wie bei allen Schleifenvariablen), so dass sie bei der Ausführung einen anderen Wert hat als bei der Erstellung.

0

Ich bin mir nicht sicher, was Sie mit Ihrer Lambda-Funktion machen wollen. Es braucht kein Argument ...

Ich denke, dass Python einen Generator ähnlich (i for i in range(10)) macht und dann das iterating. Nachdem es 10 Mal zählt, ist der endgültige Wert i 9, und das ist dann, was die Lambda-Funktion zurückgibt.

Wollten Sie irgendeine Art von Objekt, das die Zahlen von [0,9] ergeben würde? Wenn Sie dies tun wollen, verwenden Sie einfach xrange(10), was einen Iterator zurückgibt, der die Zahlen [0,9] ergibt.

def f(x): 
    return x 

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

Anmerkung: Ich denke, es ist eine sehr schlechte Idee ist j() eine Funktion zu nennen, um zu versuchen. Python verwendet j, um den Imaginärteil einer komplexen Zahl anzuzeigen, und ich denke, dass eine Funktion mit dem Namen j den Parser verwirren könnte. Ich habe seltsame Fehlermeldungen erhalten, in denen versucht wurde, den Code auszuführen.