2014-10-29 21 views
7

Mam prosty mały dekorator, który buforuje wyniki z wywołań funkcji w dict jako atrybut funkcji.Przypisanie atrybutu funkcji powoduje, że jest on niedostępny.

from decorator import decorator 
def _dynamic_programming(f, *args, **kwargs): 
    try: 
     f.cache[args] 
    except KeyError: 
     f.cache[args] = f(*args, **kwargs) 
    return f.cache[args] 

def dynamic_programming(f): 
    f.cache = {} 
    return decorator(_dynamic_programming, f) 

Chcę teraz dodać możliwość opróżnienia pamięci podręcznej. Więc zmienić funkcję dynamic_programming() tak:

def dynamic_programming(f): 
    f.cache = {} 
    def clear(): 
     f.cache = {} 
    f.clear = clear 
    return decorator(_dynamic_programming, f) 

Teraz załóżmy Używam tego drobiazg do realizacji funkcji liczb Fibonacciego:

@dynamic_programming 
def fib(n): 
    if n <= 1: 
     return 1 
    else: 
     return fib(n-1) + fib(n-2) 

>>> fib(4) 
5 
>>> fib.cache 
{(0,): 1, (1,): 1, (2,): 2, (3,): 3, (4,): 5} 

ale teraz kiedy wyczyścić coś cache dziwnego się dzieje:

>>> fib.clear() 
>>> fib.cache 
{(0,): 1, (1,): 1, (2,): 2, (3,): 3, (4,): 5} 

Albo (z nowym działającego jądra Python) zrobić to na odwrót:

>>> fib.clear() 
>>> fib(4) 
5 
>>> fib.cache 
{} 

Dlaczego nie jest cache jakoś „osiągalny” po pierwszym dostępu do niej, to znaczy nie zmieniając przy wywołaniu clear() Po rozmowy lub połączenia po clear()?

(. Btw wiem rozwiązanie poprawnie wyczyścić pamięć podręczną. Nazywając f.cache.clear() zamiast przypisywania {} aby to działa zgodnie z oczekiwaniami Jestem zainteresowany jedynie w powodu dlaczego rozwiązanie przypisanie zawiedzie.)

Odpowiedz

7

Problem dotyczy modułu decorator. Jeśli dodać kilka print sprawozdań do dekoratora:

from decorator import decorator 
def _dynamic_programming(f, *args, **kwargs): 
    print "Inside decorator", id(f.cache) 
    try: 
     f.cache[args] 
    except KeyError: 
     f.cache[args] = f(*args, **kwargs) 
    return f.cache[args] 

def dynamic_programming(f): 
    f.cache = {} 
    print "Original cache", id(f.cache) 
    def clear(): 
     f.cache = {} 
     print "New cache", id(f.cache) 
    f.clear = clear 
    return decorator(_dynamic_programming, f) 

@dynamic_programming 
def fib(n): 
    if n <= 1: 
     return 1 
    else: 
     return fib(n-1) + fib(n-2) 

print fib(4) 
print id(fib.cache) 
fib.clear() 
print id(fib.cache) 
print fib(10) 
print id(fib.cache) 

To wyjść (zduplikowane wiersze pomijane):

Original cache 139877501744024 
Inside decorator 139877501744024 
5 
139877501744024 
New cache 139877501802208 
139877501744024 
Inside decorator 139877501802208 
89 
139877501744024 

Jak widać, cache wewnątrz zmian dekorator, zgodnie z wyraźną funkcję. Jednak wersja cache dostępna pod numerem __main__ nie zmienia się. Drukowanie cache zewnątrz i wewnątrz dekoratora dać jaśniejszy obraz (ponownie, duplikaty pominięta):

Inside decorator {} 
Inside decorator {(1,): 1} 
Inside decorator {(2,): 2, (0,): 1, (1,): 1} 
Inside decorator {(2,): 2, (0,): 1, (3,): 3, (1,): 1} 
5 
Outside {(2,): 2, (0,): 1, (3,): 3, (1,): 1, (4,): 5} 
Inside decorator {} 
Inside decorator {(1,): 1} 
Inside decorator {(2,): 2, (0,): 1, (1,): 1} 
Inside decorator {(2,): 2, (0,): 1, (3,): 3, (1,): 1} 
Inside decorator {(2,): 2, (0,): 1, (3,): 3, (1,): 1, (4,): 5} 
Inside decorator {(0,): 1, (1,): 1, (2,): 2, (3,): 3, (4,): 5, (5,): 8} 
Inside decorator {(0,): 1, (1,): 1, (2,): 2, (3,): 3, (4,): 5, (5,): 8, (6,): 13} 
Inside decorator {(0,): 1, (1,): 1, (2,): 2, (3,): 3, (4,): 5, (5,): 8, (6,): 13, (7,): 21} 
Inside decorator {(0,): 1, (1,): 1, (2,): 2, (8,): 34, (3,): 3, (4,): 5, (5,): 8, (6,): 13, (7,): 21} 
Inside decorator {(0,): 1, (1,): 1, (2,): 2, (8,): 34, (3,): 3, (9,): 55, (4,): 5, (5,): 8, (6,): 13, (7,): 21} 
89 
Outside {(2,): 2, (0,): 1, (3,): 3, (1,): 1, (4,): 5} 

Jak widać, wewnętrzne zmiany nie są echem na zewnątrz. Problem polega na tym, że wewnątrz the decorator module istnieje linia (wewnątrz klasy kiedyś zrobić dekorator):

self.dict = func.__dict__.copy() 

A potem later:

func.__dict__ = getattr(self, 'dict', {}) 

Więc w zasadzie, __dict__ na zewnątrz jest inny niż __dict__ w środku.Oznacza to, że:

  • __dict__ jest kopiowany (nie odwołuje) przez dekoratora
  • Kiedy cache zmienia, zmienia wnętrze __dict__, a nie na zewnątrz __dict__
  • Dlatego cache używanego przez _dynamic_programming jest wyczyszczone, ale nie widać tego z zewnątrz, ponieważ dekorator __dict__ nadal wskazuje stary cache (jak widać powyżej, jako aktualizacje wewnątrz cache, podczas gdy zewnętrzna cache pozostaje taka sama)

Podsumowując, jest to problem z modułem decorator.

+2

Mój najdłuższy słupek jeszcze na SO , przez wygląd tego ... :) – matsjoyce

3

tak @ odpowiedź matsjoyce jest bardzo interesujący i dogłębne i wiem, że masz już rozwiązanie, ale zawsze uważają, że jest trochę jaśniej napisać własny dekoratorów:

def dynamic_programming(f): 
    def wrapper(*args, **kwargs): 
     try: 
      return wrapper.cache[args]    
     except KeyError: 
      res = wrapper.cache[args] = f(*args, **kwargs) 
      return res 
    wrapper.cache = {} 
    wrapper.clear = wrapper.cache.clear 
    return wrapper 
+2

Dobra uwaga. Z takimi problemami pomaga dostrzec wszelkie dziwactwa bez wkopania się w źródło modułu ... – matsjoyce

Powiązane problemy