2012-05-04 42 views
33

Następujący kod wypluwa 1 dwa, że ​​spodziewać się 0 i 1Pythona lambda wiązania do lokalnych wartości

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

że oczekuje lambdy Pythonowi wiązać się z odniesieniem zmienna lokalnego wskazując, za scena. Jednak wydaje się, że tak nie jest. Rozwinąłem ten problem w dużym systemie, w którym lambda wykonuje współczesną równoważność C++ wiązania (na przykład "boost :: bind"), w którym w takim przypadku wiązałbyś się z inteligentnym ptr lub kopią, kontrukując kopię dla lambda.

Jak powiązać zmienną lokalną z funkcją lambda i czy zachowuje poprawne odniesienie, gdy jest używana? Jestem dość oszołomiony tym zachowaniem, ponieważ nie spodziewałbym się tego po języku z śmieciarzem.

Kod w pytaniu wygląda następująco (l3_e jest zmienną, która jest przyczyną problemu):

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

Widziałem kilka wariantów tego pytania. ktoś musi zebrać je wszystkie, zmienić tytuł na coś niezapomnianego, a następnie powiedzieć internetowi, żeby google to – Shep

+1

ah, tutaj idziesz, w pobliżu duplikatu: [leksykalny-zamknij-w-python] (http://stackoverflow.com/q/233673/915501) – Shep

+0

Również pierwszy fragment kodu doskonale ilustruje twój punkt, dlaczego wklejasz ten drugi fragment? – Shep

Odpowiedz

59

Zmień x.append(lambda : pv(v)) do x.append(lambda v=v: pv(v)).

Spodziewamy się, że "python lambdas" będzie wiązał się z referencją, do której wskazuje lokalna zmienna, za sceną ", ale tak nie działa Python. Python sprawdza nazwę zmiennej w momencie wywołania tej funkcji, a nie po jej utworzeniu. Użycie domyślnego argumentu działa, ponieważ domyślne argumenty są obliczane podczas tworzenia funkcji, a nie po jej wywołaniu.

To nie jest coś wyjątkowego w lambdach. Rozważmy:

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

drukuje

after foo was defined 
+0

ciekawe, czy mógłbyś rozwinąć intuicję mechaniki. –

+1

Bardziej oczywistą alternatywą jest użycie 'functools.partial' lub użycie osobnej funkcji pomocnika do utworzenia zamknięć (' def make_closure (v): return lambda: pv (v) ', możesz umieścić go w' teście'). – delnan

+0

więc mój prawdziwy kod powinien "lambda par1, par2 = l3_e: self.parse_l4 (par1, par2)"? –

12

zamknięcie lambda trzyma odwołanie do zmiennej używany, a nie jej wartość, więc jeżeli wartość zmiennej późniejszymi zmianami, wartość zamknięcia zmienia także . Oznacza to, że wartość zmiennej zamknięcia jest rozwiązywana, gdy wywoływana jest funkcja, a nie podczas jej tworzenia. (Zachowanie Pythona tutaj nie jest niczym niezwykłym w funkcjonalnych programowania świata, na co warto).

Istnieją dwa rozwiązania:

  1. użyć domyślnej argumentu, oprawa aktualną wartość zmiennej lokalnej nazwisko w momencie definicji. lambda v=v: pv(v)

  2. Użyj podwójnego lambda i natychmiast zadzwoń do pierwszego. (lambda v: lambda: pv(v))(v)

Powiązane problemy