2012-05-04 7 views
33

Der folgende Code 1 zweimal ausspuckt, erwarte ich 0 zu sehen und dann 1Lambda-Python zu lokalen Werten Bindung

def pv(v) : 
    print v 


def test() : 
    value = [] 
    value.append(0) 
    value.append(1) 
    x=[] 
    for v in value : 
    x.append(lambda : pv(v)) 
    return x 

x = test() 
for xx in x: 
    xx() 

Ich erwartete Python Lambdas auf die Referenz binden eine lokale Variable zeigt, hinter der Szene. Dies scheint jedoch nicht der Fall zu sein. Ich habe dieses Problem in einem großen System entdeckt, wo das Lambda dem modernen C++ gleichwertig ist mit einer Bindung (zum Beispiel boost :: bind), wo Sie in einem solchen Fall an ein smart ptr binden oder eine Kopie für das Lambda kopieren würden.

Also, wie binde ich eine lokale Variable an eine Lambda-Funktion und habe sie die richtige Referenz beibehalten, wenn sie verwendet wird? Ich bin ziemlich betrunken mit dem Verhalten, da ich dies nicht von einer Sprache mit einem Müllsammler erwarten würde.

Der betreffende Code sieht wie folgt aus (l3_e die Variable, die das Problem verursacht):

for category in cat : 
     for l2 in cat[category].entries : 
     for l3 in cat[category].entries[l2].entry["sub_entries"] : 
      l3_e = cat[category].entries[l2].entry["sub_entries"][l3] 
      url = "http://forums.heroesofnewerth.com/" + l3_e.entry["url"] 
      self.l4_processing_status[l3_e] = 0 
      l3_discovery_requests.append(Request(
      url, callback = lambda response : self.parse_l4(response,l3_e))) 
      print l3_e.entry["url"] 
    return l3_discovery_requests 
+2

Ich habe mehrere Varianten dieser Frage gesehen. jemand muss alle von ihnen aggregieren, den Titel in etwas denkwürdiges ändern, und dann dem Internet sagen, googeln, dass – Shep

+1

ah, hier gehen Sie, in der Nähe von Duplikat: [lexical-closures-in-python] (http://stackoverflow.com/q/233673/915501) – Shep

+0

Auch der erste Code-Chunk veranschaulicht Ihren Punkt perfekt, warum einfügen in diesem zweiten Brocken? – Shep

Antwort

59

ändern x.append(lambda : pv(v)) zu x.append(lambda v=v: pv(v)).

Sie erwarten, dass "Python-Lambdas an die Referenz binden, auf die eine lokale Variable verweist, hinter der Szene", aber so funktioniert Python nicht. Python sucht den Variablennamen zum Zeitpunkt des Aufrufs der Funktion, nicht beim Erstellen. Die Verwendung eines Standardarguments funktioniert, da Standardargumente beim Erstellen der Funktion und nicht beim Aufruf ausgewertet werden.

Dies ist nichts besonderes an Lambdas. Bedenken Sie:

x = "before foo defined" 
def foo(): 
    print x 
x = "after foo was defined" 
foo() 

druckt

after foo was defined 
+0

Interessant, könnten Sie bitte auf die Intuition der Mechanik eingehen. –

+1

Eine offensichtlichere Alternative ist die Verwendung von 'functools.partial' oder die Verwendung einer separaten Hilfsfunktion, um die Closures zu erzeugen (' def make_closure (v): return lambda: pv (v) ', du kannst es in' test' setzen). – delnan

+0

also sollte mein realer Code 'Lambda par1, par2 = l3_e: self.parse_l4 (par1, par2)'? –

12

Die Schließung des Lambda enthält einen Verweis auf die Variable verwendet wird, nicht seinen Wert, so dass, wenn der Wert der Variablen später ändert, auch der Wert im Verschluß ändert . Das heißt, der Wert der Abschlussvariablen wird aufgelöst, wenn die Funktion aufgerufen wird, nicht wenn sie erstellt wird. (Python Verhalten hier ist nicht ungewöhnlich in der funktionalen Programmierung Welt, für das, was es wert ist.)

Es gibt zwei Lösungen:

  1. ein Standardargument verwenden, um den aktuellen Wert der Variablen auf eine lokale Bindung Name zum Zeitpunkt der Definition. lambda v=v: pv(v)

  2. Verwenden Sie ein Doppel-Lambda und rufen Sie sofort das erste. (lambda v: lambda: pv(v))(v)

Verwandte Themen