2009-07-09 15 views
6

Chciałbym napisać dekorator, który ograniczy liczbę razy funkcja może być wykonana, coś w następującej składni:W jaki sposób są napisane te typy dekoratorów Pythona?


@max_execs(5) 
def my_method(*a,**k): 
    # do something here 
    pass 

myślę, że to możliwe, aby napisać ten typ dekoratora, ale don Nie wiem jak. Myślę, że funkcja nie będzie pierwszym argumentem dekoratora, prawda? Chciałbym „zwykły” dekorator realizacji, a nie jakąś klasę z połączeń metody.

Powodem tego jest to, aby dowiedzieć się, jak są one napisane. Proszę wyjaśnić składnię i sposób działania tego dekoratora.

Odpowiedz

12

To właśnie bita. Nie używa klasę, ale nie używać atrybutów funkcja:

def max_execs(n=5): 
    def decorator(fn): 
     fn.max = n 
     fn.called = 0 
     def wrapped(*args, **kwargs): 
      fn.called += 1 
      if fn.called <= fn.max: 
       return fn(*args, **kwargs) 
      else: 
       # Replace with your own exception, or something 
       # else that you want to happen when the limit 
       # is reached 
       raise RuntimeError("max executions exceeded") 
     return wrapped 
    return decorator 

max_execs zwraca funkcjonował nazywa decorator, co z kolei zwraca wrapped.decoration przechowuje max execs i aktualną liczbę exec w dwóch atrybutach funkcji, które następnie są sprawdzane w wrapped.

Tłumaczenie: Przy użyciu dekorator tak:

@max_execs(5) 
def f(): 
    print "hi!" 

jesteś w zasadzie robi coś takiego:

f = max_execs(5)(f) 
+0

podczas stosowania tego dekoratora, w jaki sposób kod jest "tłumaczony" przez Pythona? Na przykład, jeśli moja metoda nazywa się blabla i zastosuję atrybut max_execs, w jaki sposób Python to zobaczy? blabla = max_execs (5) (blabla)? – Geo

+1

Zaktualizowałem odpowiedź, aby dołączyć tłumaczenie, ale masz odpowiedni pomysł. – mipadi

0

Wiem, mówiłeś, że nie chciał klasę, ale niestety jest to jedyny sposób mogę myśleć jak to zrobić poza czubek mojej głowy.

class mymethodwrapper: 
    def __init__(self): 
     self.maxcalls = 0 
    def mymethod(self): 
     self.maxcalls += 1 
     if self.maxcalls > 5: 
      return 
     #rest of your code 
     print "Code fired!" 

Ogień go jak ten

a = mymethodwrapper 
for x in range(1000): 
    a.mymethod() 

Wyjście będzie:

>>> Code fired! 
>>> Code fired! 
>>> Code fired! 
>>> Code fired! 
>>> Code fired! 
+0

nie może być używana z żadną metodą i używa obiektu wywoływalnego. Chciałbym regularnego dekoratora. – Geo

4

Dekorator jest jedynie wywoływalnym który przekształca funkcję w coś innego. W twoim przypadku, max_execs(5) musi być wywołaniem, które przekształca funkcję w inny obiekt wywoływalny, który będzie liczyć i przekazywać połączenia.

class helper: 
    def __init__(self, i, fn): 
     self.i = i 
     self.fn = fn 
    def __call__(self, *args, **kwargs): 
     if self.i > 0: 
      self.i = self.i - 1 
      return self.fn(*args, **kwargs) 

class max_execs: 
    def __init__(self, i): 
     self.i = i 
    def __call__(self, fn): 
     return helper(self.i, fn) 

Nie rozumiem, dlaczego chciałbyś ograniczyć się do funkcji (a nie klasy). Ale jeśli naprawdę chcesz ...

def max_execs(n): 
    return lambda fn, i=n: return helper(i, fn) 
3

Istnieją dwa sposoby na zrobienie tego. Sposób obiektowego jest, aby Klasa:

class max_execs: 
    def __init__(self, max_executions): 
     self.max_executions = max_executions 
     self.executions = 0 

    def __call__(self, func): 
     @wraps(func) 
     def maybe(*args, **kwargs): 
      if self.executions < self.max_executions: 
       self.executions += 1 
       return func(*args, **kwargs) 
      else: 
       print "fail" 
     return maybe 

Zobacz this question o wyjaśnienie wraps.

Wolę wyżej podejście OOP dla tego rodzaju dekorator, odkąd w zasadzie dostałem prywatną zmienną count śledzenie liczby egzekucji. Jednak innym podejściem jest użycie zamknięcia, takiego jak, na przykład,

Dotyczyło to trzech funkcji. Funkcja max_execs otrzymuje parametr określający liczbę wykonań i zwraca dekorator, który ograniczy liczbę takich połączeń. Ta funkcja, actual_decorator, robi to samo co nasza metoda __call__ w przykładzie OOP. Jedynym dziwactwem jest to, że ponieważ nie mamy klasy z prywatnymi zmiennymi, musimy zmutować zmienną executions, która znajduje się w zewnętrznym zasięgu naszego zamknięcia. Python 3.0 obsługuje tego ze stwierdzeniem nonlocal, ale w Pythonie 2.6 lub wcześniej, musimy owinąć nasze egzekucje liczyć na liście tak, że może być zmutowany.

+1

jeśli na przykład metoda z atrybutem @logged oznacza method = logged (method), w jaki sposób metoda z @max_execs (5) jest "przetłumaczona"? – Geo

+0

To samo dotyczy powiedzenia max_execs (5) (f) –

2

Bez opierając się do stanu, w klasie, trzeba zapisz stan (liczenie) w samej funkcji:

def max_execs(count): 
    def new_meth(meth): 
     meth.count = count 
     def new(*a,**k): 
      meth.count -= 1 
      print meth.count    
      if meth.count>=0: 
       return meth(*a,**k) 
     return new 
    return new_meth 

@max_execs(5) 
def f(): 
    print "invoked" 

[f() for _ in range(10)] 

Daje to:

5 
invoked 
4 
invoked 
3 
invoked 
2 
invoked 
1 
invoked 
0 
-1 
-2 
-3 
-4 
+0

Ups, mipadi było szybsze dzięki podobnemu rozwiązaniu. –

1

Ta metoda nie modyfikuje funkcji, lecz zawija ją na obiekt wywoływalny.

Używanie klasy spowalnia wykonywanie o ~ 20% w porównaniu z użyciem funkcji załatania!

def max_execs(n=1): 
    class limit_wrapper: 
     def __init__(self, fn, max): 
      self.calls_left = max 
      self.fn = fn 
     def __call__(self,*a,**kw): 
      if self.calls_left > 0: 
       self.calls_left -= 1 
       return self.fn(*a,**kw) 
      raise Exception("max num of calls is %d" % self.i) 


    def decorator(fn): 
     return limit_wrapper(fn,n) 

    return decorator 

@max_execs(2) 
def fun(): 
    print "called" 
Powiązane problemy