Изменение декоратора кулдауна для работы вместо методов вместо

Я пытаюсь создать декоратор, который будет работать на методы, чтобы применить к ним «кулдаун», то есть они не могут быть вызваны несколько раз в течение определенной продолжительности. Я уже создал один для функций:

>>> @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() >>> tf(1) <Test object at ...> 1 >>> tf(2) >>> tf(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 и полностью игнорирует исходное _CooldownFunction . Как мне заставить его работать с методами, правильно передавая оригинальное self а _CooldownFunction объект _CooldownFunction ?

Кроме того, пользователям необходимо иметь возможность изменять оставшееся время «на лету», что делает это еще сложнее (не может просто использовать __get__ для возврата functools.partial(self.__call__, obj) или что-то еще):

 >>> class Test: ... @cooldown(10) ... def f(self, arg): ... print(self, arg) ... >>> t = Test() >>> tf(5) <Test object at ...> 5 >>> tfremaining = 0 >>> tf(3) # Almost immediately after previous call <Test object at ...> 3 

Изменить: он должен работать только для методов, а не для обоих методов и функций.

Изменить 2: В этом дизайне есть огромный недостаток. Хотя он работает нормально для обычных функций, я хочу, чтобы он украшал каждый экземпляр отдельно. В настоящее время, если мне нужно иметь два экземпляра t1 и t2 и должны были вызвать t1.f() , я больше не мог вызывать t2.f() потому что кулдаун является тейдом для метода f() вместо экземпляров. Я, вероятно, мог бы использовать для этого какой-то словарь, но после этого осознания я еще больше потерялся …

3 Solutions collect form web for “Изменение декоратора кулдауна для работы вместо методов вместо”

Вы можете переопределить метод __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 

Исправляя проблему 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 

EDIT: я изменил код, поэтому он работает независимо для нескольких экземпляров, помещая его в аргумент self . Обратите внимание, что это чисто полагается на первый аргумент, который называется «я», но вы можете искать более надежный способ обнаружения, если украшенный вызываемый метод или функция, если вам нужна дополнительная защита здесь.

EDIT2: У этого может быть ошибка, если вы выполните instance1.foo() а затем попробуйте сделать instance2.foo.setRemaining(0) . Поскольку контекст не переключился, это установило бы оставшееся значение для instance1. Может быть исправлено, если сеттеры и геттеры привязали методы к контексту, но это становится беспорядочным. Я остановлюсь здесь сейчас

Этот декоратор работает как с функциями, так и с методами, поддерживает 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) >>> aa() Method 7 >>> aa() >>> aaremaining 3.589237892348223 >>> aaremaining = 10 >>> aa(32) >>> aaremaining 8.423482288923785 >>> aaremaining = 0 >>> aa(32) Method 7 32 
  • Как временно назначить переменные-члены?
  • Задача сельдерея с несколькими декораторами не автоматическая регистрация имени задачи
  • Пример декоратора Python
  • Как определить, была ли функция определена локально?
  • Большинство Pythonic способ предоставить метаданные функции во время компиляции?
  • Как я могу реализовать собственный обработчик ошибок для всех ошибок HTTP в Flask?
  • Является ли Python Decorator таким же, как аннотация Java, или Java с аспектами?
  • python и pygame - что произошло во время выполнения этой оболочки?
  • Python - лучший язык программирования в мире.