語法回顧
開始以前咱們再將Python裝飾器的語法回顧一下。python
@decorate def f(...): pass
等同於:markdown
def f(...): pass f = decorate(f)
@語法的好處在於:閉包
- 相同的函數名只出現一次,避免了
f = decorate(f)
這樣的語句。 - 可讀性更高,讓讀代碼的人一眼就明白函數被裝飾了哪些功能。
@call()裝飾器
假設你要建立一個整數平方的列表,你能夠這樣寫:app
>>> table = [0, 1, 4, 9, 16] >>> len(table), table[3] (5, 9)
也能夠使用列表表達式,由於咱們要實現比較簡單。函數
>>> table = [i * i for i in range(5)] >>> len(table), table[3] (5, 9)
可是假如這個列表的邏輯比較複雜的時候,最好是寫成一個方法,這樣會更好維護。post
>>> def table(n): ... value = [] ... for i in range(n): ... value.append(i*i) ... return value >>> table = table(5)
注意看最後一句,是否是很符合裝飾器的語法規則?什麼狀況下你會寫這樣的代碼呢?測試
- 你須要把相對複雜業務寫成一個方法。
- 這個方法和返回值能夠同名,並且你不但願對外公開此方法,只公開結果。
- 你想盡可能使用裝飾器。(無厘頭的理由)
那麼這時候@call()
裝飾器就登場了。spa
def call(*args, **kwargs): def call_fn(fn): return fn(*args, **kwargs) return call_fn
這個裝飾器會把你傳入的參數送給目標函數而後直接執行。code
@call(5) def table(n): value = [] for i in range(n): value.append(i*i) return value print len(table), table[3] # 5 9
@call()
裝飾器適用於任何函數,你傳入的參數會被直接使用而後結果賦值給同名函數。這樣避免了你從新定義一個變量來存儲結果。對象
@list 裝飾器
假如你有一個這樣一個生成器函數。
def table(n): for i in range(n): yield i
當你要生成n=5
的序列時,能夠直接調用。
table = table(5) print table # <generator object table at 0x027DAC10>
使用上節提到的@call()
裝飾器,也能獲得同樣的結果。
@call(5) def table(n): for i in range(n): yield i print table # <generator object table at 0x0340AC10>
你還能夠直接將其轉換成列表。(使用list(generator_object)
函數)
@list @call(5) def table(n): for i in range(n): yield i print table # [0, 1, 2, 3, 4]
相信很多同窗第一次看到這個用法應該是懵逼的。這等同於列表表達式,可是可讀性也許差了很多。例子自己只是演示了裝飾器的一種用法,但不是推薦你就這樣使用裝飾器。你這樣用也許會被其餘同事拖到牆角里打死。
類裝飾器
在Python 2.6之前,還不支持類裝飾器。也就是說,你不能使用這樣的寫法。
@decorator class MyClass(object): pass
你必須這樣寫:
class MyClass(object): pass MyClass = decorator(MyClass)
也就是說,@語法對類是作了特殊處理的,類不必定是一個callable對象(儘管它有構造函數),可是也容許使用裝飾器。那麼基於以上語法,你以爲類裝飾器能實現什麼功能呢?
舉一個例子,ptest中的@TestClass()
用於聲明一個測試類,其源代碼大體如此。
def TestClass(enabled=True, run_mode="singleline"): def tracer(cls): cls.__pd_type__ ='test' cls.__enabled__ = enabled cls.__run_mode__ = run_mode.lower() return cls return tracer
當咱們在寫一個測試類時,發生了什麼?
@TestClass() class TestCases(object): # your test case ... print TestCases.__dict__ # {'__module__': '__main__', '__enabled__': True, '__pd_type__': 'test', '__run_mode__': 'singleline', ...}
竟然裝飾器的參數全都變成了變成這個類的屬性,好神奇!咱們把語法糖一一展開。
class TestCases(object): pass decorator = TestClass() print decorator # <function tracer at 0x033128F0> TestCases = decorator(TestCases) print TestCases # <class '__main__.TestCases'> print TestCases.__dict__ # {'__module__': '__main__', '__enabled__': True, '__pd_type__': 'test', '__run_mode__': 'singleline', ...}
當裝飾器在被使用時,TestClass()
函數會立刻被執行並返回一個裝飾器函數,這個函數是一個閉包函數,保存了enabled
和run_mode
兩個變量。另外它還接受一個類做爲參數,並使用以前保存的變量爲這個類添加屬性,最後返回。因此通過@TestClass()
裝飾過的類都會帶上__enabled__
、__pd_type__
以及__run_mode__
的屬性。
因而可知,類裝飾器能夠完成和Java相似的註解功能,並且要比註解強大的多。
後記
裝飾器就是一個語法糖,當你看不懂一個裝飾器時,能夠考慮將其依次展開,分別帶入。這個語法糖給了咱們很多方便,可是也要慎用。畢竟可維護的代碼纔是高質量的代碼。