2015-11-27 26 views
6

Belirli bir süre içinde birden çok kez çağrılmayacakları anlamına gelen bir "cooldown" uygulayabilecek yöntemler için çalışacak bir dekoratör oluşturmaya çalışıyorum. Zaten işlevleri için birini oluşturdu:İşlev yerine işlevler için bir cooldown dekoratörün değiştirilmesi

>>> @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 

ama bu normal fonksiyonlarını yerine sınıfların yöntemleri desteklemek gerekir:

:

İşte
>>> 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 

Bu normal fonksiyonları için çalışmasını sağlamak için oluşturulan ne

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 

Ancak, _CooldownFunction nesnesini self olarak geçirdiği ve orijinal self tamamen yok sadığından, bu yöntemlerle çalışmaz. _CooldownFunction nesnesi yerine orijinal self düzgün şekilde geçirerek, yöntemleri ile çalışmak için nasıl alabilirim?

>>> 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 

:

Ayrıca, bu daha da zor (sadece functools.partial(self.__call__, obj) falan dönmek için __get__ kullanamazsınız) yapar sinek, kalan süreyi değiştirmek için muktedir kullanıcılar için gereklidir Düzenleme: Her iki yöntem ve işlev için değil, yalnızca yöntemler için çalışmak gerekir.

Düzenleme 2: Başlamak için bu tasarımda büyük bir hata var. Normal işlevler için iyi çalışıyor olsa da, her bir örneği ayrı ayrı dekore etmesini istiyorum. Şu anda iki örneğim t1 ve t2 ve t1.f()'u çağırıyor olsaydım, artık cooldown örnekleri yerine f() yöntemine eklendiğinden, t2.f() numaralı telefonu arayabilirdim. Bunun için bir çeşit sözlük kullanabilirdim, ama bu gerçekleşmeden sonra daha da kayboldum ...

+0

Not. Testinizi bir kapamaya kapatıp geri döndürmeyi denediniz mi? – Felk

+0

@Felk 'cooldown' işlevi,' _CooldownFunc' sınıfı değil, dekoratördür. 'Cooldown' dekoratör, orijinal işlevi bir '_CooldownFunc' nesnesiyle değiştirir. Bu nedenle, orijinal işlevi çağırmaya çalıştığınızda, aslında, _cooldownFunc' nesnesinin '__call__' işlevini çağırıyorsunuz. Yani evet, "__call__", süslü işlevi çağırdığınız her zaman çağrılır. Normal işlevler için mükemmel çalışıyor, ancak 'self' parametresi sorun yaratıyor. –

+0

Oh, bunu özledim, üzgünüm. Ama sonra tekrar bir kez 'cooldown' dekoratör şimdi 'func' yaptığı gibi her bir çağırma için yeni bir '_CooldownFunc' örneği yaratmamalı, değil mi? – Felk

cevap

1

Bir tanımlayıcı yapmak için sınıfınızın __get__ yöntemini geçersiz kılabilirsiniz. Biri dekore edilmiş yöntemi içerdiği nesneden aldığında ve içerdiği nesneyi geçtiğinde __get__ metodu çağrılır, daha sonra orijinal yönteme geçebilirsiniz. İhtiyacınız olan işlevselliği sağlayan bir nesne döndürür.

def __get__(self, obj, objtype): 
    return Wrapper(self, obj) 

Wrapper nesne __call__ uygular ve istediğiniz herhangi bir özellikleri, böylece nesnesine bu uygulamaları taşıyın.

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 
+0

Bu, düşünmediğim gerçekten güzel bir yaklaşım. Ancak, sorgunun sonunda gösterilen "kalan" özelliğine erişmeme izin vermiyor. Bu programımın hayati bir parçası. –

+0

@MarkusMeskanen Ah, sorunun bir bölümünü fark etmedim. Bir özellik yerine getter/setter işlevlerini kullanmak yeterli ise, bunları kolayca ekleyebilirsiniz (ör. "Inner.get_remaining = lambda: cf.remaining"). Aksi halde nasıl uygulanabileceğinden emin değilim. – interjay

+0

Bu, hiçbir şeyden daha iyi, ama yine de mükemmel değil. Bekleyeceğim ve eğer ben veya başkasının mülkün kullanılmasına izin verecek bir çözüm bulabileceğini düşüneceğim. –

1

konu interjay ele Tespit Şimdi işlevleri/yöntemlerin her türlü işleri sizin cooldown dekoratör, hızlı bir yeniden yazma yaptı:

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 

Testler gibi görünecektir:

@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) 

çıkışlar:

foo: 1 
foo: 3 
foo: 4 
bar: 1 
bar: 2 
bar: 3 

DÜZENLEME: Kodu değiştirdim, böylece bu işlevi, çağrılan işlevin self bağımsız değişkenine depolayarak birden çok örnek için bağımsız olarak çalışır.Unutmayın ki bu sadece "self" ismindeki ilk argümana dayanmaktadır, ancak burada daha fazla güvenliğe ihtiyacınız varsa, dekore edilmiş bir callable'ın bir yöntem mi yoksa bir işlev mi olduğunu saptamanın daha sağlam bir yolunu arayabilirsiniz.

EDIT2: Bu, instance1.foo() yaparsanız ve instance2.foo.setRemaining(0) yapmayı denediğinizde bir hatayla karşılaşabilir. Bağlam değişmediğinden, bu örnek 1 için kalan değeri belirler. Kural koyucuları ve alıcıları bağlama yöntemlerini bağlamda oluşturarak düzeltilebilir, ancak bu durum dağınık hale gelir. Burada duracağım

+0

Bu yaklaşımın ne yazık ki “kalan” özelliğini desteklemediğini düşünüyorum. –

+0

Şimdi yapar. Ben sadece dekore edilmiş işleve getters ve setters ekledim. Ne yazık ki, nasıl ve hatta bir özellik olarak setters ve setters eklemek mümkün olacağını anlayamadım – Felk

+0

Özgün soruya benim düzenleme bakın, dikkatsiz ve bu aracılığıyla düşünmedim, orijinal kodum didn istediğim gibi çalışıyorum. Her bir örnek için ayrı bir cooldown'a sahip olması gerekir. Bu artık mümkün mü? : D –

0

Bu dekoratör, fonksiyon ve yöntemlerle çalışır, remaining özelliğini destekler ve tek bir sınıf olarak uygulanır.

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 
dekoratör en `sadece sonradan denir oluyor işlevi dönen bir kez çağrılan __call__` o
# 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