我正在尝试将可选参数传递给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)
。我尝试了很多解决方案,并阅读了有关它的文章,但在这里我仍然无法使其发挥作用。
有什么想法解决这个问题吗?谢谢!
答案 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_hits
和timeout
参数的装饰工厂,并返回装饰器:
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>