2011-09-20 28 views
11

Sto provando a passare argomenti facoltativi al mio decoratore di classi in python. sotto il codice ho attualmente:Argomenti decoratore classe Python

class Cache(object): 
    def __init__(self, function, max_hits=10, timeout=5): 
     self.function = function 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, *args): 
     # Here the code returning the correct thing. 


@Cache 
def double(x): 
    return x * 2 

@Cache(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 

Il secondo decoratore con argomenti per sovrascrivere quello di default (max_hits=10, timeout=5 nella mia funzione __init__), non funziona e ho avuto l'eccezione TypeError: __init__() takes at least 2 arguments (3 given). Ho provato molte soluzioni e ho letto articoli a riguardo, ma qui non riesco ancora a farlo funzionare.

Qualche idea per risolvere questo? Grazie!

risposta

12

@Cache(max_hits=100, timeout=50) chiamate __init__(max_hits=100, timeout=50), quindi non si soddisfa l'argomento function.

È possibile implementare il decoratore tramite un metodo wrapper che rileva se è presente una funzione. Se trova una funzione, può restituire l'oggetto Cache. Altrimenti, può restituire una funzione wrapper che verrà utilizzata come decoratore.

class _Cache(object): 
    def __init__(self, function, max_hits=10, timeout=5): 
     self.function = function 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, *args): 
     # Here the code returning the correct thing. 

# wrap _Cache to allow for deferred calling 
def Cache(function=None, max_hits=10, timeout=5): 
    if function: 
     return _Cache(function) 
    else: 
     def wrapper(function): 
      return _Cache(function, max_hits, timeout) 

     return wrapper 

@Cache 
def double(x): 
    return x * 2 

@Cache(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 
+0

Grazie ragazzi e @lunixbochs per la vostra soluzione! Funziona come un fascino :) – Dachmt

+3

Se lo sviluppatore chiama 'Cache' con argomenti posizionali invece di parole chiave (es. '@Cache (100,50)'), a 'funzione' verrà assegnato il valore 100 e' max_hits' 50. An l'errore non verrà generato fino alla chiamata della funzione. Questo potrebbe essere considerato un comportamento sorprendente poiché la maggior parte delle persone si aspetta una semantica di parole chiave e posizionale uniforme. – unutbu

11
@Cache 
def double(...): 
    ... 

è equivalente a

def double(...): 
    ... 
double=Cache(double) 

Mentre

@Cache(max_hits=100, timeout=50) 
def double(...): 
    ... 

è equivalente a

def double(...): 
    ... 
double = Cache(max_hits=100, timeout=50)(double) 

Cache(max_hits=100, timeout=50)(double) ha una semantica molto diversa da Cache(double).

Non è saggio provare a rendere Cache gestire entrambi i casi di utilizzo.

Si potrebbe invece utilizzare una fabbrica decoratore che può prendere opzionali max_hits e timeout argomenti, e restituisce un decoratore:

class Cache(object): 
    def __init__(self, function, max_hits=10, timeout=5): 
     self.function = function 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, *args): 
     # Here the code returning the correct thing. 

def cache_hits(max_hits=10, timeout=5): 
    def _cache(function): 
     return Cache(function,max_hits,timeout) 
    return _cache 

@cache_hits() 
def double(x): 
    return x * 2 

@cache_hits(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 

PS. Se la classe Cache non ha altri metodi oltre a __init__ e __call__, probabilmente è possibile spostare tutto il codice all'interno della funzione _cache ed eliminare del tutto Cache.

+1

imprudente o meno ... se lo sviluppatore usa accidentalmente @cache invece di cache(), farà un errore strano quando provano a chiamare la funzione risultante. l'altra implementazione funziona come cache e cache() – lunixbochs

+0

Grazie a @unutbu, buona soluzione anche. – Dachmt

+1

@lunixbochs: uno sviluppatore che confonde 'cache_hits' (nee' cache') con 'cache_hits()' ha la stessa probabilità di confondere qualsiasi oggetto funzione con una chiamata di funzione, o di scambiare un generatore con un iteratore. Anche i programmatori Python moderatamente esperti dovrebbero essere usati per prestare attenzione alla differenza. – unutbu

0

Ho imparato molto da questa domanda, grazie a tutti. Non è la risposta solo per mettere parentesi vuote sul primo @Cache? Quindi è possibile spostare il parametro function su __call__.

class Cache(object): 
    def __init__(self, max_hits=10, timeout=5): 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, function, *args): 
     # Here the code returning the correct thing. 

@Cache() 
def double(x): 
    return x * 2 

@Cache(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 

Anche se credo che questo approccio è più semplice e più concisa:

def cache(max_hits=10, timeout=5): 
    def caching_decorator(fn): 
     def decorated_fn(*args ,**kwargs): 
      # Here the code returning the correct thing. 
     return decorated_fn 
    return decorator 

Se si dimentica le parentesi quando si utilizza il decoratore, purtroppo ancora non si ottiene un errore fino al runtime, come l'esterno i parametri del decoratore sono passati alla funzione che stai cercando di decorare.Poi in fase di esecuzione il decoratore interno si lamenta:

TypeError: caching_decorator() takes exactly 1 argument (0 given).

Tuttavia si può prendere questo, se si conosce i parametri del decoratore sono mai andare a essere un callable:

def cache(max_hits=10, timeout=5): 
    assert not callable(max_hits), "@cache passed a callable - did you forget to parenthesize?" 
    def caching_decorator(fn): 
     def decorated_fn(*args ,**kwargs): 
      # Here the code returning the correct thing. 
     return decorated_fn 
    return decorator 

Se ora si tenta:

@cache 
def some_method() 
    pass 

Ottieni un AssertionError in dichiarazione.

Su una tangente totale, mi sono imbattuto in questo post alla ricerca di decoratori che decorano le classi, piuttosto che le classi che decorano. Nel caso lo faccia anche qualcun altro, this question è utile.

Problemi correlati