2014-10-29 7 views
7

Ho un semplice decoratore, che memorizza i risultati delle chiamate di funzione in un dict come attributo di funzione.Riassegna un attributo di funzione lo rende "irraggiungibile"

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) 

Ora desidero aggiungere la possibilità di svuotare la cache. Così a cambiare la funzione dynamic_programming() in questo modo:

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

Ora supponiamo che uso questa piccola cosa per implementare una funzione numero di Fibonacci:

@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} 

Ma ora quando svuotare la cache qualcosa di strano succede:

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

Or (con un nuovo kernel Python in esecuzione) fanno il contrario:

Perché la cache in qualche modo non è "raggiungibile" dopo il primo accesso ad essa, vale a dire non cambia quando si chiama clear() dopo una chiamata o una chiamata dopo un clear()?

(. Btw So che una soluzione per cancellare la cache in modo corretto:. Chiamando f.cache.clear() invece di assegnare {} ad esso funziona come previsto Sto semplicemente interessato al ragione perché la soluzione assegnazione non riesce.)

risposta

7

Il problema è con il modulo decorator. Se si aggiungono alcuni print dichiarazioni al vostro decoratore:

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) 

E 'uscite (righe duplicate saltati):

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

Come si può vedere, la cache all'interno delle modifiche decoratore secondo la la funzione di cancellazione. Tuttavia, lo cache accesso da __main__ non cambia. Stampa del cache fuori e dentro il decoratore dare un quadro più chiaro (ancora una volta, i duplicati saltati):

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} 

Come si può vedere, i cambiamenti all'interno non sono eco sulla parte esterna. Il problema è che all'interno the decorator module, c'è la linea (all'interno della classe è usato per fare il decoratore):

self.dict = func.__dict__.copy() 

E poi later:

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

Quindi, in pratica, il __dict__ sulla parte esterna è diverso dallo __dict__ all'interno.Questo significa che:

  • Il __dict__ viene copiata (non riferimento) dal decoratore
  • Quando i cache cambia, cambia l'interno __dict__, non l'esterno __dict__
  • Pertanto, il cache utilizzato dal _dynamic_programming è deselezionata, ma non è possibile vederla dall'esterno, poiché il del decoratore punta ancora al vecchio cache (come si può vedere sopra, come gli aggiornamenti interni cache, mentre l'esterno cache rimane lo stesso)

Quindi, per riassumere, si tratta di un problema con il modulo decorator.

+2

Il mio più lunga dopo ancora su SO , per l'aspetto di esso ... :) – matsjoyce

3

così @ risposta di matsjoyce è molto interessante e approfondito e so che già la soluzione, ma trovo sempre un po 'più chiaro di scrivere il mio decoratori:

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

Buon punto. Con problemi come questo, aiuta a vedere tutte le stranezze senza scavare nella fonte di un modulo ... – matsjoyce