Python类装饰器参数

时间:2011-09-20 21:34:06

标签: python arguments decorator

我正在尝试将可选参数传递给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

第二个带有参数的装饰器覆盖默认值(max_hits=10, timeout=5函数中的__init__),但是没有工作,我得到了异常TypeError: __init__() takes at least 2 arguments (3 given)。我尝试了很多解决方案,并阅读了有关它的文章,但在这里我仍然无法使其发挥作用。

有什么想法解决这个问题吗?谢谢!

5 个答案:

答案 0 :(得分:14)

@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

答案 1 :(得分:13)

@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。< / p>

答案 2 :(得分:2)

我从这个问题中学到了很多,谢谢大家。答案只是将空括号放在第一个@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()只取1个参数(给定0)。

但是如果你知道装饰器的参数永远不会是可调用的,那么你可以理解这一点:

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很有用。

答案 3 :(得分:0)

我宁愿在类的__call__方法中包含包装器:

更新: 此方法已在python 3.6中进行了测试,因此我不确定更高或更低的版本。

class Cache:
    def __init__(self, max_hits=10, timeout=5):
        # Remove function from here and add it to the __call__
        self.max_hits = max_hits
        self.timeout = timeout
        self.cache = {}

    def __call__(self, function):
        def wrapper(*args):
            value = function(*args)
            # saving to cache codes
            return value
        return wrapper

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

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

答案 4 :(得分:0)

定义带有可选参数的装饰器:

from functools import wraps, partial             
def _cache(func=None, *, instance=None):         
    if func is None:                             
        return partial(_cache, instance=instance)
    @wraps(func)                                 
    def wrapper(*ar, **kw):                      
        print(instance)                          
        return func(*ar, **kw)                   
    return wrapper         

并将 instance 对象传递给 __call__ 中的装饰器,或者使用在每个 __call__ 上实例化的其他帮助器类。这样你就可以使用不带括号的装饰器,带参数,甚至可以在代理缓存类中定义一个 __getattr__ 来应用一些参数。

class Cache:                                   
    def __call__(self, *ar, **kw):             
        return _cache(*ar, instance=self, **kw)
                                               
cache = Cache()                                
                                               
@cache                                         
def f(): pass                                  
f() # prints <__main__.Cache object at 0x7f5c1bde4880>