2011-09-20 127 views
11

我试图将可选参数传递给我的类装饰器在python中。 下面的代码我目前有: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 

第二个装饰用的参数覆盖(在我__init__功能max_hits=10, timeout=5)默认的,不工作,我得到异常TypeError: __init__() takes at least 2 arguments (3 given)。我尝试了很多解决方案,并阅读了关于它的文章,但在这里我仍然无法完成工作。

任何想法解决这个?谢谢!

回答

12

@Cache(max_hits=100, timeout=50)来电__init__(max_hits=100, timeout=50),所以你不满足function的说法。

您可以通过检测函数是否存在的包装器方法来实现您的装饰器。如果它找到一个函数,它可以返回Cache对象。否则,它可以返回一个将用作装饰器的包装函数。

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

感谢你们和@lunixbochs的解决方案!像一个魅力工作:) – Dachmt

+3

如果开发人员用位置而不是关键字参数调用'Cache'(例如'@Cache(100,50)'),那么'function'将被分配值100和'max_hits' 50.一个在调用该函数之前不会引发错误。这可以被认为是令人惊讶的行为,因为大多数人期望统一的位置和关键字语义。 – unutbu

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

相当于

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

虽然

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

相当于

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

Cache(max_hits=100, timeout=50)(double)Cache(double)具有非常不同的语义。

试图让Cache处理这两种用例是不明智的。

你也可以使用一个装饰的工厂,可以采取可选max_hitstimeout参数,并返回一个装饰:

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。如果Cache类别除__init____call__之外没有其他方法,则可以移动_cache函数中的所有代码,并删除Cache

+1

不明智或不...如果开发人员意外地使用@cache而不是cache(),当他们尝试调用结果函数时会发生奇怪的错误。其他实现实际上同时作为缓存和缓存() – lunixbochs

+0

感谢@unutbu,也是很好的解决方案。 – Dachmt

+1

@lunixbochs:将'cache_hits'(nee'cache')与'cache_hits()'混淆的开发者可能会将任何函数对象与函数调用混淆,或者将生成器与迭代器错误。即使是经验丰富的Python程序员也应该注意差异。 – unutbu

0

我从这个问题中学到了很多,谢谢大家。是不是只是在第一个@Cache上放置了空括号?然后您可以将function参数移动到__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 

虽然我认为这种做法是更简单,更简洁:

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 

如果您使用的装饰时,忘记了括号,不幸的是你还没有得到一个错误,直到运行时,如外装饰参数传递你正在试图装饰的功能。然后在运行时内装饰抱怨:

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

但是你能赶上这一点,如果你知道你的装饰器的参数永远不会是一个可调用:

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 

如果现在尝试:

@cache 
def some_method() 
    pass 

你得到一个AssertionError声明。

在整个切线上,我遇到了这个帖子,寻找装饰类的装饰器,而不是装饰的类。如果其他人也这样做,this question是有用的。