【发布时间】:2023-04-01 08:29:01
【问题描述】:
我正在尝试创建一个装饰器,该装饰器适用于对它们应用“冷却”的方法,这意味着它们不能在一定时间内被多次调用。我已经为函数创建了一个:
>>> @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) 或其他东西):
>>> 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(),因为冷却时间是f() 方法而不是实例。我可能可以为此使用某种字典,但在意识到这一点之后,我更加迷失了......
【问题讨论】:
-
请注意,装饰器的
__call__只被调用一次,返回之后被调用的函数。您是否尝试过将您的测试封闭在一个闭包中并返回它? -
@Felk
cooldown函数是装饰器,而不是_CooldownFunc类。cooldown装饰器将原始函数替换为_CooldownFunc对象。因此,当您尝试调用原始函数时,实际上是在调用_CooldownFunc对象的__call__,它检查冷却时间并在内部调用存储在_func属性中的原始函数。所以是的,每次调用装饰函数时都会调用__call__。它对于正常功能非常有效,但是方法的self参数带来了麻烦。 -
哦,我错过了,对不起。但话又说回来,
cooldown装饰器不应该像现在一样为每次调用func创建一个新的_CooldownFunc实例,对吧? -
@Felk 当然应该,
_CooldownFunc包含各个功能的持续时间和开始时间。如果我想用冷却时间装饰多个函数,我需要它们都有单独的开始时间和持续时间,因此我需要为每个函数创建一个新的_CooldownFunc对象。 -
@Felk Ups 我想我误读了你的问题。它不会为某个函数的每次调用创建一个新的
_CooldownFunc实例,而是为每个函数创建一个_CooldownFunc的新实例。它基本上将一个函数作为输入,并用_CooldownFunc对象替换该函数,然后可以像原来的输入函数一样调用它。
标签: python python-3.x python-decorators