修改冷却装饰器以用于方法而不是函数

Mar*_*nen 7 python python-3.x python-decorators

我正在尝试创建一个装饰器,它可以用于对它们应用"冷却"的方法,这意味着它们不能在一定的持续时间内被多次调用.我已经为函数创建了一个:

>>> @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
Run Code Online (Sandbox Code Playgroud)

但是我需要这个来支持类的方法而不是普通的函数:

>>> 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
Run Code Online (Sandbox Code Playgroud)

这是我创建的,使其适用于正常功能:

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
Run Code Online (Sandbox Code Playgroud)

但这不适用于方法,因为它将_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
Run Code Online (Sandbox Code Playgroud)

编辑:它只需要为方法工作,而不是为方法和函数工作.

编辑2:这个设计有一个巨大的缺陷.虽然它适用于普通函数,我希望它分别装饰每个实例.目前,如果我有两个实例t1并且t2要打电话t1.f(),我无法再打电话,t2.f()因为冷却时间是f()方法而不是实例.我可能会为此使用某种字典,但在这种认识之后我更加迷失......

int*_*jay 1

您可以重写类的__get__方法以使其成为描述符。当有人__get__从其包含对象中获取装饰方法并传递包含对象时,将调用该方法,然后您可以将其传递给原始方法。它返回一个实现您需要的功能的对象。

def __get__(self, obj, objtype):
    return Wrapper(self, obj)
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)