我为此编写了一个助手装饰器:
from functools import update_wrapper
class ClassWrapper:
def __init__(self, cls):
self.cls = cls
def __call__(self, *args, **kwargs):
class ClassWrapperInner:
def __init__(self, cls, *args, **kwargs):
self._cls = cls
self.args = args
self.kwargs = kwargs
def __call__(self, func, *args, **kw):
assert len(args) == 0 and len(kw) == 0 and callable(func), f"{self._cls.__name__} got invalid arguments. Did you forget to parenthesize?"
obj = self._cls(func, *self.args, **self.kwargs)
update_wrapper(obj, func)
return obj
return ClassWrapperInner(self.cls, *args, **kwargs)
在执行上下文中,这段奇怪的代码更有意义:
double = ClassWrapper(Cache)(max_hits=100, timeout=50)(double)
ClassWrapper.__init__
存储它将要包装的类(Cache)。
ClassWrapper.__call__
将它的参数(max_hits=100, timeout=50)传递给 ClassWrapperInner.__init__
,后者会为下一次调用存储它们。
ClassWrapper.__call__
将之前所有的参数和 (func) 组合在一起,并将它们传递给你的类 Cache 的一个实例,然后将其作为新的 double
返回。它还使用 functools 库更新了你类的参数 __name__ 和 __doc__。这有点像比 2D 列表展平更复杂的版本,其中是函数参数而不是列表。
通过使用这个类来装饰它,你原始的函数将按预期工作,只是在所有情况下需要在其周围加上括号。
@ClassWrapper
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):
...
@Cache()
def double(x):
return x * 2
@Cache(max_hits=100, timeout=50)
def double(x):
return x * 2
你可以尝试编辑
ClassWrapperInner.__call__
,以便不需要使用括号调用函数,但这种方法是hacky的,并且并不是很合理;这就像尝试为类的每个方法添加逻辑,以便在没有self参数的情况下正确地调用它们。
编辑:
撰写本答案后,我意识到有一种更好的方法可以制作装饰器:
def class_wrapper(cls):
def decorator1(*args, **kwargs):
def decorator2(func):
return cls(func, *args, **kwargs)
return decorator2
return decorator1
使用functools函数来更新名称和内容:
def class_wrapper(cls):
def decorator1(*args, **kwargs):
@wraps(cls)
def decorator2(func):
obj = cls(func, *args, **kwargs)
update_wrapper(obj, func)
return obj
return decorator2
return decorator1
Cache
(例如@Cache(100,50)
),则function
将被赋值为100,max_hits
为50。直到调用函数时才会引发错误。这可能被视为令人惊讶的行为,因为大多数人都期望统一的位置和关键字语义。 - unutbu__call__
方法不会接收到被装饰对象的self引用。在这种情况下无法工作。 - MichaelMoserdef Cache(function=None, *, max_hits=10, timeout=5):
- undefined