2011-09-20 22 views
11

Estoy tratando de pasar argumentos opcionales a mi decorador de clases en python. A continuación el código que tengo actualmente:Argumentos del decorador de clases de 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 

El segundo decoradora con argumentos para sobrescribir el valor predeterminado (max_hits=10, timeout=5 en mi función __init__), no está funcionando y me dieron la excepción TypeError: __init__() takes at least 2 arguments (3 given). Intenté muchas soluciones y leí artículos al respecto, pero aquí todavía no puedo hacer que funcione.

¿Alguna idea para resolver esto? ¡Gracias!

Respuesta

12

@Cache(max_hits=100, timeout=50) llama a __init__(max_hits=100, timeout=50), por lo que no está cumpliendo el argumento function.

Puede implementar su decorador a través de un método de envoltura que detecta si una función está presente. Si encuentra una función, puede devolver el objeto Cache. De lo contrario, puede devolver una función de envoltura que se utilizará como decorador.

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

¡Gracias chicos y @lunixbochs por su solución! Funciona como un encanto :) – Dachmt

+3

Si el desarrollador llama 'Cache' con argumentos posicionales en lugar de palabras clave (por ejemplo '@Cache (100,50)') entonces 'function' se le asignará el valor 100, y' max_hits' 50. An el error no se levantará hasta que se llame a la función. Esto podría considerarse un comportamiento sorprendente, ya que la mayoría de la gente espera una semántica posicional y de palabras clave uniforme. – unutbu

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

es equivalente a

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

Mientras

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

es equivalente a

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

Cache(max_hits=100, timeout=50)(double) tiene una semántica muy diferente a Cache(double).

No es prudente tratar de hacer que Cache maneje ambos casos de uso.

en su lugar podría usar una fábrica decorador que puede tomar opcionales max_hits y timeout argumentos, y devuelve un decorador:

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. Si la clase Cache no tiene otros métodos además de __init__ y __call__, probablemente pueda mover todo el código dentro de la función _cache y eliminar Cache en total.

+1

imprudente o no ... si el desarrollador accidentalmente utiliza @cache en lugar de caché(), se creará un error extraño cuando intenten llamar a la función resultante. la otra implementación realmente funciona como caché y caché() – lunixbochs

+0

Gracias @unutbu, buena solución también. – Dachmt

+1

@lunixbochs: Un desarrollador que confunde 'cache_hits' (nee' cache') con 'cache_hits()' puede confundir cualquier objeto de función con una llamada de función, o confundir un generador con un iterador. Incluso los programadores de Python con experiencia moderada deberían acostumbrarse a prestar atención a la diferencia. – unutbu

0

He aprendido mucho de esta pregunta, gracias a todos. ¿No es la respuesta solo para poner paréntesis vacíos en el primer @Cache? Luego puede mover el parámetro function al __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 

Aunque creo que este enfoque es más simple y 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 

Si se olvida de los paréntesis cuando se utiliza el decorador, por desgracia, todavía no reciben un error hasta que el tiempo de ejecución, como el exterior a los parámetros del decorador se les pasa la función que intenta decorar.A continuación, en tiempo de ejecución el decorador interior se queja:

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

Sin embargo, usted puede coger esto, si usted sabe los parámetros de su decorador nunca van a ser exigible:

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 

Si ahora trata:

@cache 
def some_method() 
    pass 

Obtiene una declaración AssertionError en la declaración.

En una tangente total, me encontré con esta publicación en busca de decoradores que decoran las clases, en lugar de las clases que decoran. En caso de que alguien más también lo haga, this question es útil.

Cuestiones relacionadas