2011-07-13 47 views
27

在Python中有兩種方式申報的裝飾:類裝飾VS功能裝飾

基於類

class mydecorator(object): 
    def __init__(self, f): 
     self.f = f 

    def __call__(self, *k, **kw): 
     # before f actions 
     self.f(*k, **kw) 
     # after f actions 

功能基於

def mydecorator(f): 
    def decorator(*k, **kw): 
     # before f actions 
     f(*k, **kw) 
     # after f actions 

    return decorator   

是否有這些有什麼區別聲明? 在哪些情況下應該使用它們中的每一種?

+1

使用類,您可以使用描述符協議! – phant0m

+0

@ phant0m:在缺點方面,你有*要使用描述符協議的類(否則你的裝飾器將不能完全在方法上工作)。 –

+0

@Rosh:嗯是的,你在這裏有一個非常強大的點:) – phant0m

回答

18

如果你想保持裝飾器的狀態,你應該使用一個類。

例如,這不起作用

def mydecorator(f): 
    x = 0 
    def decorator(): 
     x += 1 # x is a nonlocal name and cant be modified 
     return f(x) 
    return decorator 

這有很多解決方法,但最簡單的方法是使用一類

class mydecorator(object): 
    def __init__(self, f): 
     self.f = f 
     self.x = 0 

    def __call__(self, *k, **kw): 
     self.x += 1 
     return f(self.x) 
+2

我會認爲最簡單的方法就是說'nonlocal x; x + = 1'。或者這在3以下的Python版本中不起作用? - 是的,看起來像'nonlocal'是在Python 3中引入的。 – JAB

+0

有關方法裝飾器限制的更多信息,請參見http://stackoverflow.com/questions/6676015/class-decorators-vs-function-decorators/6677524# 6677524答。 – Yossi

+1

一個由多個模塊使用的有狀態裝飾器使得它的任何用法都取決於*順序,其中使用它的其他模塊被導入到程序的其他地方,包括在完全不相關的模塊中**!*所以我的第一個經驗法則是,如果你想保持裝飾者的狀態,「停下來思考這是否是最好的方法」。有時候當然會,在這種情況下,你應該使用類裝飾器。 – Ben

6

實際上沒有「兩種方式」。只有一種方法(定義一個可調用的對象),或者與python中創建可調用對象的方法一樣多(它可以是其他對象的方法,lambda表達式的結果,「部分」對象,任何可贖回)。

函數定義是製作可調用對象最簡單的方法,並且作爲最簡單的方法,在大多數情況下可能是最好的。使用類爲您提供了更多可能性,以便更簡潔地編寫更復雜的案例(即使在最簡單的情況下,它看起來非常優雅),但它並沒有那麼明顯。

+0

+1深刻的回答 –

2

不,有(多於)兩種方式來製作可調用對象。一個是def函數,顯然是可調用的。另一種方法是在一個類中定義一個__call__方法,該方法將調用它的實例。類本身是可調用的對象。

裝飾器只不過是一個可調用的對象,它是打算接受函數作爲其唯一參數並返回可調用的東西。語法如下:

@decorate 
def some_function(...): 
    ... 

只是一個寫的稍微更好的方式:

def some_function(...): 
    ... 

some_function = decorate(some_function) 

你給基於類的例子不是一個函數,它的功能和返回功能,這是bog標準的vanilla裝飾器,它是一個使用實例可調用的函數初始化的類。對我來說,如果你沒有真正使用它作爲一個類,這有點奇怪(它是否有其他方法?它的狀態是否改變?你是否創建了幾個具有通用行爲封裝的實例?)。但是,正常使用你的裝飾函數並不能說明區別(除非它是一個特別的入侵裝飾),所以對你來說,做一些更自然的事情。

1

讓我們來測試它!

test_class = """ 
class mydecorator_class(object): 
    def __init__(self, f): 
     self.f = f 

    def __call__(self, *k, **kw): 
     # before f actions 
     print 'hi class' 
     self.f(*k, **kw) 
     print 'goodbye class' 
     # after f actions 

@mydecorator_class 
def cls(): 
    print 'class' 

cls() 
""" 

test_deco = """ 
def mydecorator_func(f): 
    def decorator(*k, **kw): 
     # before f actions 
     print 'hi function' 
     f(*k, **kw) 
     print 'goodbye function' 
     # after f actions 
    return decorator 

@mydecorator_func 
def fun(): 
    print 'func' 

fun() 
""" 

if __name__ == "__main__": 
    import timeit 
    r = timeit.Timer(test_class).timeit(1000) 
    r2 = timeit.Timer(test_deco).timeit(1000) 
    print r, r2 

我已經得到的結果是這樣的:0.0499339103699 0.0824959278107

這意味着,裝飾類2倍的速度?

+2

有趣,但我想它是特定於實現。對於其他python實現(Jython,PyPy)或甚至不同版本的CPython,您可能會得到完全不同的結果。 另外,這可能是運行'mydecorator_func'和類定義代碼的時間不同。這只是應用程序的啓動時間,通常並不重要。在兩種情況下,修飾的函數調用速度可能都是相同的。 –

16

當你創建一個可調用的返回另一個調用,功能方法更簡單,更便宜。有兩個主要區別:

  1. 功能的方法與方式自動工作,而如果你使用你的類的方法,你必須閱讀描述符和定義__get__方法。
  2. 類的方法使保持狀態更容易。你可以使用閉包,特別是在Python 3中,但是通常使用類方法來保持狀態。

此外,該功能的方法允許你返回原功能,修改,或存放後。

但是,裝飾器可以返回除可調用對象之外的其他東西更多而不是可調用對象。有了課程,您可以:

  1. 向裝飾可調用對象添加方法和屬性,或對它們執行操作(uh-oh)。
  2. 創建描述符,當放置在類中時以特殊方式進行操作(例如,classmethod,property
  3. 使用繼承來實現類似但不同的裝飾器。

如果您有任何疑問,可以問問自己:您是否希望裝飾器返回一個函數,該函數的功能與函數完全相同?使用函數返回一個函數。你想讓你的裝飾器返回一個自定義對象,它可以做更多或不同於某個函數的東西嗎?創建一個類並將其用作裝飾器。

+0

+1令人震驚的是,沒有人注意到方法問題! – delnan

+1

+1非常完整的答案。晚會之後,但這應該是被接受的答案。 –

+0

「函數方法自動地與方法一起工作」這是一個重要的區別。謝謝! –