2009-07-09 106 views
6

我想寫一個裝飾將限制次數可以執行的功能,以及語法如下一些:這些類型的python裝飾器是如何編寫的?


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

我認爲這是可能寫這種類型的裝飾,但我不不知道如何。我認爲一個函數不會是這個裝飾器的第一個參數,對吧?我想要一個「普通的裝飾器」的實現,而不是一些有調用方法的類。

原因是要了解它們是如何書寫的。請解釋語法,以及裝飾器的工作原理。

回答

12

這是我鞭打。它不使用類,但它確實使用功能屬性:

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返回運作稱爲decorator,這反過來又返回wrappeddecoration在兩個函數屬性中存儲最大執行數和當前exec數,然後在wrapped中進行檢查。

翻譯:當使用像這樣的裝飾:

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

你基本上做這樣的事情:

f = max_execs(5)(f) 
+0

一樣,python代碼是如何「翻譯」的?例如,如果我的方法被稱爲blabla,並且我應用了max_execs屬性,那麼Python將如何看待它? blabla = max_execs(5)(blabla)? – Geo 2009-07-09 20:35:05

0

我知道你說過你不想上課,但不幸的是,這是我能想到如何從頭頂開始做的唯一方法。

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

火起來像這樣

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

輸出將是:

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

這不能用於任何方法,並使用可調用的對象。我想要一個普通的裝飾者。 – Geo 2009-07-09 20:28:36

4

裝飾僅僅是一種把函數轉換成別的東西調用。在你的情況下,max_execs(5)必須是一個可調用函數,它將函數轉換爲另一個可調用的對象,該對象將計算和轉發調用。

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) 

我不明白你爲什麼會想限制自己的功能(而不是類)。但如果你真的想...

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

有兩種方法可以做到這一點。面向對象的方法是讓一個類:

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 

wraps的說明,請參見this question

我更喜歡上面這種裝飾器的OOP方法,因爲你基本上有一個跟蹤執行次數的私有計數變量。但是,另一種方法是使用閉包,如

def max_execs(max_executions): 
    executions = [0] 
    def actual_decorator(func): 
     @wraps(func) 
     def maybe(*args, **kwargs): 
      if executions[0] < max_executions: 
       executions[0] += 1 
       return func(*args, **kwargs) 
      else: 
       print "fail" 
     return maybe 
    return actual_decorator 

這涉及三個功能。 max_execs函數被賦予一個執行次數的參數,並返回一個裝飾器,它將限制你進行那麼多的調用。該功能actual_decorator與OOP示例中的__call__方法具有相同的功能。唯一不可思議的是,由於我們沒有私有變量的類,因此我們需要修改位於閉包外部範圍內的executions變量。 Python 3.0支持nonlocal聲明,但在Python 2.6或更早版本中,我們需要將我們的執行計數包含在列表中,以便它可以進行變異。

+1

如果例如具有@logged屬性的方法表示method = logged(method),那麼具有@max_execs(5)「方法」的方法是如何「翻譯」的? – Geo 2009-07-09 20:31:05

+0

這和應用這個裝飾器時說的max_execs(5)(f) – 2009-07-09 20:37:41

2

不依靠這樣的狀態,一類,你必須保存功能本身的狀態(計數):

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

它給出:

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

此方法不會修改函數內部,而是將其包裝到可調用對象中。

與使用修補功能相比,使用class減慢了執行20%!

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"