2012-08-29 18 views
8

Właśnie nauczyłem się Pythona @ dekoratora, jest fajnie, ale wkrótce odkryłem, że mój zmodyfikowany kod wychodzi z dziwnych problemów.Funkcja zamykania Pythona utrata zewnętrznej zmiennej dostępu

def with_wrapper(param1): 
    def dummy_wrapper(fn): 
     print param1 
     param1 = 'new' 
     fn(param1) 
    return dummy_wrapper 

def dummy(): 
    @with_wrapper('param1') 
    def implementation(param2): 
     print param2 

dummy() 

debugować go, wyrzuca wyjątek w druku param1

UnboundLocalError: local variable 'param1' referenced before assignment 

Jeśli usunąć param1 = 'new' tej linii, bez jakiejkolwiek operacji modyfikacji (link do nowego obiektu) na zmiennych z zakresu zewnętrznego, to rutyna może działać.

Czy to znaczy, że wykonałem tylko jedną kopię zewnętrznych zmiennych zakresu, a następnie dokonałem modyfikacji?


Dzięki Delnan, niezbędne jest zamknięcie. Prawdopodobna odpowiedź stąd: What limitations have closures in Python compared to language X closures?

Podobny kod jako:

def e(a): 
    def f(): 
     print a 
     a = '1' 
    f() 
e('2') 

a także wydaje poprzednią zmienną globalną irytujące:

a = '1' 
def b(): 
    #global a 
    print a 
    a = '2' 
b() 

ta jest ustalana przez dodanie globalnego symbol. Ale do zamknięcia nie znaleziono takiego symbolu. Dzięki unutbu, Python 3 dał nam nielokalny.

Wiem, że z góry bezpośredni dostęp do zmiennej zewnętrznej jest tylko do odczytu. ale jest to niewygodne, ponieważ zmienia się również odczytana zmienna odczytu (print var).

+0

możliwe duplikat [Co ograniczenia mieć zamknięcia w Pythonie w porównaniu z zamknięciami języka X?] (http://stackoverflow.com/questions/141642/what-limitations-have-closures-in-python-compared-to-language-x-closures) – delnan

+0

To absolutnie nic wspólnego z dekoratorami. – delnan

+0

Tak, dzieje się to w zamknięciach. tak: def e (a): def f(): Wydrukuj a = '1' f() e ('123') –

Odpowiedz

11

Gdy Pythona przetwarza funkcji zauważa gdy stwierdzi zmienną się po stronie lewej przydziału, takie jak

param1 = 'new' 

Zakłada się, że wszystkie takie zmienne lokalnie w funkcji. Więc kiedy poprzedzać to zadanie z

print param1 

błąd występuje, ponieważ Python nie posiada wartości tej zmiennej lokalnej w czasie oświadczenie druk jest wykonywany.


W Python3 można naprawić to, oświadczając, że param1 jest nielokalna:

def with_wrapper(param1): 
    def dummy_wrapper(fn): 
     nonlocal param1 
     print param1 
     param1 = 'new' 
     fn(param1) 
    return dummy_wrapper 

W python2 trzeba uciekać się do sztuczki, takie jak przepuszczanie param1 wewnątrz listy (lub niektóre inne zmienne object):

def with_wrapper(param1_list): 
    def dummy_wrapper(fn): 
     print param1_list[0] 
     param1_list[0] = 'new' # mutate the value inside the list 
     fn(param1_list[0]) 
    return dummy_wrapper 

def dummy(): 
    @with_wrapper(['param1']) # <--- Note we pass a list here 
    def implementation(param2): 
     print param2 
+0

Jaki jest dobry sposób na "naprawienie" tego? – Silox

+1

... wstawić instrukcję 'print' po przypisaniu? – kindall

+0

Dzięki za wyjaśnienie. Ale nie mam pojęcia, dlaczego param1 = 'new' przypisuje zewnętrzny parametr, ale żeby utworzyć jeden lokalny var. –

0

przypisać param1 w funkcji, co sprawia, param1 Zmienna lokalna. Jednak nie został on przypisany w miejscu, w którym go drukujesz, więc pojawia się błąd. Python nie powraca do szukania zmiennych w zakresach zewnętrznych.

+0

_print param1_ jest jednym z przykładów, w prawdziwym kodzie, jakiekolwiek odniesienie do param1 jest zabronione, gdy istnieje param1 = xx –

1

Jeśli chcesz uchwycić zmienną z zewnętrznego zakresu w Pythonie 2.x, użycie opcji globalnej jest również opcją (ze zwykłymi zastrzeżeniami - ale przydatna w przypadku sytuacji tymczasowych lub kodu eksploracyjnego).

Choć dodaje rzuci (cesja outer1 obrębie wewnętrzna sprawia, że ​​lokalne i stąd nieograniczona w jeśli warunek):

def outer(): 
    outer1 = 1 
    def inner(): 
     if outer1 == 1: 
      outer1 = 2 
      print('attempted to accessed outer %d' % outer1) 

To nie będzie:

def outer(): 
    global outer1 
    outer1 = 1 
    def inner(): 
     global outer1 
     if outer1 == 1: 
      outer1 = 2 
      print('accessed outer %d' % outer1)