我正在尝试创建一个装饰器,它可以用于对它们应用“冷却”的方法,这意味着它们不能在一定的持续时间内被多次调用。我已经为函数创建了一个:
>>> @cooldown(5)
... def f():
... print('f() was called')
...
>>> f()
f() was called
>>> f() # Nothing happens when called immediately
>>> f() # This is 5 seconds after first call
f() was called
但是我需要这个来支持类的方法而不是普通的函数:
>>> class Test:
... @cooldown(6)
... def f(self, arg):
... print(self, arg)
...
>>> t = Test()
>>> t.f(1)
<Test object at ...> 1
>>> t.f(2)
>>> t.f(5) # Later
<Test object at ...> 5
这是我为使其适用于普通功能而创建的内容:
import time
class _CooldownFunc:
def __init__(self, func, duration):
self._func = func
self.duration = duration
self._start_time = 0
@property
def remaining(self):
return self.duration - (time.time() - self._start_time)
@remaining.setter
def remaining(self, value):
self._start_time = time.time() - (self.duration - value)
def __call__(self, *args, **kwargs):
if self.remaining <= 0:
self.remaining = self.duration
return self._func(*args, **kwargs)
def __getattr__(self, attr):
return self._func.__getattribute__(attr)
def cooldown(duration):
def decorator(func):
return _CooldownFunc(func, duration)
return decorator
但这不适用于方法,因为它将_CooldownFunction
对象作为self
传递,并完全忽略原始self
。
如何使用方法,正确传递原始self
而不是_CooldownFunction
对象?
此外,用户需要能够动态更改剩余时间,这会更加困难(不能只使用__get__
返回functools.partial(self.__call__, obj)
或其他内容):< / p>
>>> class Test:
... @cooldown(10)
... def f(self, arg):
... print(self, arg)
...
>>> t = Test()
>>> t.f(5)
<Test object at ...> 5
>>> t.f.remaining = 0
>>> t.f(3) # Almost immediately after previous call
<Test object at ...> 3
编辑它只需要为方法工作,而不是为方法和函数工作。
编辑2:这个设计中存在一个巨大的缺陷。虽然它适用于普通函数,我希望它分别装饰每个实例。目前,如果我有两个t1
和t2
个实例并且要拨打t1.f()
,我就无法再拨打t2.f()
,因为冷却时间是{{1}方法而不是实例。我可能会为此使用某种字典,但在实现之后我更加迷失......
答案 0 :(得分:1)
您可以覆盖类的__get__
方法,使其成为描述符。当有人从其包含的对象中获取装饰方法时,将调用__get__
方法,并传递包含对象,然后您可以将其传递给原始方法。它返回一个实现所需功能的对象。
def __get__(self, obj, objtype):
return Wrapper(self, obj)
Wrapper
对象实现了__call__
以及您想要的任何属性,因此将这些实现移动到该对象中。它看起来像是:
class Wrapper:
def __init__(self, cdfunc, obj):
self.cdfunc = cdfunc
self.obj = obj
def __call__(self, *args, **kwargs):
#do stuff...
self.cdfunc._func(self.obj, *args, **kwargs)
@property
def remaining(self):
#...get needed things from self.cdfunc
答案 1 :(得分:1)
解决问题interjay问题,我快速重写你的冷却装饰器,现在适用于所有类型的功能/方法:
class cooldown(object):
def __init__(self, duration):
self._duration = duration
self._storage = self
self._start_time = 0
def __getRemaining(self):
if not hasattr(self._storage, "_start_time"):
self._storage._start_time = 0
return self._duration - (time.time() -
self._storage._start_time)
def __setRemaining(self, value):
self._storage._start_time = time.time() - (self._duration -
value)
remaining = property(__getRemaining, __setRemaining)
def __call__(self, func):
is_method = inspect.getargspec(func).args[0] == 'self'
def call_if(*args, **kwargs):
if is_method :
self._storage = args[0]
else:
self._storage = self
if self.remaining <= 0:
self.remaining = self._duration
return func(*args, **kwargs)
call_if.setRemaining = self.__setRemaining
call_if.getRemaining = self.__getRemaining
return call_if
试验:
@cooldown(2)
def foo(stuff):
print("foo: %s" % stuff)
foo(1)
foo(2)
time.sleep(3)
foo(3)
foo.setRemaining(0)
foo(4)
class Bla(object):
@cooldown(2)
def bar(self, stuff):
print("bar: %s" % stuff)
bla = Bla()
bla.bar(1)
bla.bar.setRemaining(0)
bla.bar(2)
time.sleep(3)
bla.bar(3)
bla.bar(4)
输出:
foo: 1
foo: 3
foo: 4
bar: 1
bar: 2
bar: 3
编辑:我改变了代码,因此它可以通过将其存储到调用函数的self
参数中来独立地处理多个实例。请注意,这纯粹依赖于名为&#34; self&#34;的第一个参数,但如果您需要更高的安全性,可以搜索更健壮的方法来检测装饰的callable是方法还是函数。 / p>
EDIT2:如果您执行instance1.foo()
然后尝试执行instance2.foo.setRemaining(0)
,则可能存在错误。由于上下文没有被切换,这将设置instance1的剩余值。可以通过将setter和getters绑定方法绑定到上下文来修复,但这变得很混乱。我现在就到此为止
答案 2 :(得分:0)
这个装饰器兼容函数和方法,支持remaining
属性,并作为单个类实现。
import time
class cooldown:
def __init__(self, timeout):
self.timeout = timeout
self.calltime = time.time() - timeout
self.func = None
self.obj = None
def __call__(self, *args, **kwargs):
if self.func is None:
self.func = args[0]
return self
now = time.time()
if now - self.calltime >= self.timeout:
self.calltime = now
if self.obj is None:
return self.func.__call__(*args, **kwargs)
else:
return self.func.__get__(self.obj, self.objtype)(*args, **kwargs)
def __get__(self, obj, objtype):
self.obj = obj
self.objtype = objtype
return self
@property
def remaining(self):
now = time.time()
delta = now - self.calltime
if delta >= self.timeout:
return 0
return self.timeout - delta
@remaining.setter
def remaining(self, value):
self.calltime = time.time() - self.timeout + value
# test with functions
@cooldown(8)
def test(*args):
print('Function', *args)
>>> test()
Function
>>> test()
>>> test.remaining
4.718205213546753
>>> test.remaining = 0
>>> test()
Function
# test with methods
class A:
def __init__(self, value):
self.value = value
@cooldown(5)
def a(self, *args):
print('Method', self.value, *args)
>>> a = A(7)
>>> a.a()
Method 7
>>> a.a()
>>> a.a.remaining
3.589237892348223
>>> a.a.remaining = 10
>>> a.a(32)
>>> a.a.remaining
8.423482288923785
>>> a.a.remaining = 0
>>> a.a(32)
Method 7 32