2013-11-04 16 views
6

Ich stolperte über ein Verhalten in Python, dass ich ein schweres Verständnis habe. Dies ist der Proof-of-Concept-Code:Weird Lambda Verhalten in Schleifen

from functools import partial 

if __name__ == '__main__': 
    sequence = ['foo', 'bar', 'spam'] 
    loop_one = lambda seq: [lambda: el for el in seq] 
    no_op = lambda x: x 
    loop_two = lambda seq: [partial(no_op, el) for el in seq] 
    for func in (loop_one, loop_two): 
     print [f() for f in func(sequence)] 

Der Ausgang der oben ist:

['spam', 'spam', 'spam'] 
['foo', 'bar', 'spam'] 

Das Verhalten von loop_one ist überraschend für mich, wie ich es erwarten würde, wie loop_two verhalten: el ist ein unveränderlicher Wert (ein String), der sich bei jeder Schleife ändert, aber lambda scheint einen Zeiger auf die "Schleifenvariable" zu speichern, als würde die Schleife die gleiche Speicheradresse für jedes Element der Sequenz wiederverwenden.

Das obige Verhalten ist das gleiche mit ausgewachsenen Funktionen mit einer for-Schleife in ihnen (so ist es nicht eine Liste Verständnis Syntax).

Aber warten Sie: es gibt mehr ... und mehr rätselhaft!

Das folgende Skript funktioniert wie loop_one:

b = [] 
for foo in ("foo", "bar"): 
    b.append(lambda: foo) 

print [a() for a in b] 

(Ausgabe: ['bar', 'bar'])

Aber sehen, was passiert, wenn man die Variablennamen foo mit a ersetzen:

b = [] 
for a in ("foo", "bar"): 
    b.append(lambda: a) 

print [a() for a in b] 

(Ausgabe: [<function <lambda> at 0x25cce60>, <function <lambda> at 0x25cced8>])

Eine Idee von dem, was hier passiert? Ich vermute, dass es ein Problem mit der zugrundeliegenden C-Implementierung meines Interpreters geben muss, aber ich habe nichts anderes (Jthon, PyPy oder Ähnliches), um zu testen, ob dieses Verhalten in verschiedenen Implementierungen konsistent ist.

Antwort

4

Die Funktion lambda: el in loop_one verwendet, bezieht sich auf eine Variable el die nicht in dem lokalen Bereich festgelegt wird. Daher sieht Python für sie als nächste in dem umschließenden Umfang der anderen lambda:

lambda seq: [lambda: el for el in seq] 

nach dem sogenannten LEGB rule.

Zu der Zeit lambda: el heißt, wurde dieses umschließende Lambda (natürlich) schon aufgerufen und das Listenverständnis wurde ausgewertet. Die im Listenverständnis verwendete el ist eine lokale Variable in diesem einschließenden Lambda. Sein Wert ist der Wert, der zurückgegeben wird, wenn Python den Wert el in lambda: el sucht. Dieser Wert für el ist der gleiche für alle anderen lambda: el Funktionen im Listenverständnis: Es ist der letzte Wert el in der for el in seq Schleife zugewiesen.Somit ist el immer 'spam', der letzte Wert in seq.


Sie haben bereits festgestellt, ein Problem zu umgehen, einen Verschluss, wie Ihre loop_two zu verwenden. Eine andere Möglichkeit besteht darin, el als lokale Variable mit einem Standardwert zu definieren:

loop_one = lambda seq: [lambda el=el: el for el in seq] 
3

Die Variablen (foo im folgenden Beispiel) wird nicht gebunden, wenn das Lambda erstellt wird, aber wenn das Lambda aufgerufen wird.

>>> b = [] 
>>> for foo in ("foo", "bar"): 
...  b.append(lambda: foo) 
... 
>>> foo = "spam" 
>>> print [a() for a in b] 
['spam', 'spam'] 

>>> b = [] 
>>> for foo in ("foo", "bar"): 
...  b.append(lambda foo=foo: foo) 
... 
>>> print [a() for a in b] 
['foo', 'bar']