原文出處: cicaday
之前有比較系統(tǒng)介紹過Python的裝飾器(請查閱《詳解Python裝飾器》)镊逝,本文算是一個補充物臂。今天我們一起探討一下裝飾器的另類用法旺拉。
語法回顧
開始之前我們再將Python裝飾器的語法回顧一下。
@ decorate
def f(...):
pass
等同于:
def f(...):
pass
f = decorate(f)
@語法的好處在于:
- 相同的函數(shù)名只出現(xiàn)一次棵磷,避免了f = decorate(f)這樣的語句蛾狗。
- 可讀性更高,讓讀代碼的人一眼就明白函數(shù)被裝飾了哪些功能仪媒。
@call()裝飾器
假設(shè)你要創(chuàng)建一個整數(shù)平方的列表沉桌,你可以這樣寫:
>>> table = [0, 1, 4, 9, 16]
>>> len(table), table[3]
(5, 9)
也可以使用列表表達式,因為我們要實現(xiàn)比較簡單算吩。
>>> table = [i * i for i in range(5)]
>>> len(table), table[3]
(5, 9)
但是假如這個列表的邏輯比較復雜的時候留凭,最好是寫成一個方法,這樣會更好維護偎巢。
>>> def table(n):
... value = []
... for i in range(n):
... value.append(i*i)
... return value
>>> table = table(5)
注意看最后一句蔼夜,是不是很符合裝飾器的語法規(guī)則?什么情況下你會寫這樣的代碼呢压昼?
- 你需要把相對復雜業(yè)務寫成一個方法求冷。
- 這個方法和返回值可以同名瘤运,而且你不希望對外公開此方法,只公開結(jié)果匠题。
- 你想盡量使用裝飾器拯坟。(無厘頭的理由)
那么這時候@call()裝飾器就登場了。
def call(*args, **kwargs):
def call_fn(fn):
return fn(*args, **kwargs)
return call_fn
這個裝飾器會把你傳入的參數(shù)送給目標函數(shù)然后直接執(zhí)行韭山。
@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()裝飾器適用于任何函數(shù)郁季,你傳入的參數(shù)會被直接使用然后結(jié)果賦值給同名函數(shù)。這樣避免了你重新定義一個變量來存儲結(jié)果钱磅。
@list 裝飾器
假如你有一個這樣一個生成器函數(shù)梦裂。
def table(n):
for i in range(n):
yield i
當你要生成n=5的序列時,可以直接調(diào)用续搀。
table = table(5)
print table # <generator object table at 0x027DAC10>
使用上節(jié)提到的@call()裝飾器塞琼,也能得到一樣的結(jié)果。
@call(5)
def table(n):
for i in range(n):
yield i
print table # <generator object table at 0x0340AC10>
你還可以直接將其轉(zhuǎn)換成列表禁舷。(使用list(generator_object)函數(shù))
<a >@list</a>
@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對象(盡管它有構(gòu)造函數(shù))土童,但是也允許使用裝飾器。那么基于以上語法工坊,你覺得類裝飾器能實現(xiàn)什么功能呢献汗?
舉一個例子,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
當我們在寫一個測試類時,發(fā)生了什么昭齐?
@TestClass()
class TestCases(object):
# your test case ...
print TestCases.__dict__ # {'__module__': '__main__', '__enabled__': True, '__pd_type__': 'test', '__run_mode__': 'singleline', ...}
居然裝飾器的參數(shù)全都變成了變成這個類的屬性尿招,好神奇!我們把語法糖一一展開。
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()函數(shù)會馬上被執(zhí)行并返回一個裝飾器函數(shù)把沼,這個函數(shù)是一個閉包函數(shù),保存了enabled和run_mode兩個變量吁伺。另外它還接受一個類作為參數(shù),并使用之前保存的變量為這個類添加屬性租谈,最后返回篮奄。所以經(jīng)過@TestClass()裝飾過的類都會帶上__enabled__
、__pd_type__
以及__run_mode__
的屬性割去。
由此可見窟却,類裝飾器可以完成和Java類似的注解功能,而且要比注解強大的多呻逆。
后記
裝飾器就是一個語法糖夸赫,當你看不懂一個裝飾器時,可以考慮將其依次展開咖城,分別帶入茬腿。這個語法糖給了我們不少方便,但是也要慎用宜雀。畢竟可維護的代碼才是高質(zhì)量的代碼切平。
PyChina將聯(lián)合JetBrain(出品PyCharm的公司)一起在北京舉辦一次Python沙龍活動。
時間:11月26日晚上19:00-21:00
地點:科技寺北新橋 北京市東城區(qū)東四北大街107號科林大廈B座107室(近北新橋地鐵站)
歡迎大家報名參加本次活動辐董,特別需要志愿者來幫忙組織本次活動悴品。
詳情請點擊此處