裝飾器
上一篇文章將通過(guò)解決一個(gè)需求問(wèn)題來(lái)了解了閉包,本文也將一樣,通過(guò)慢慢演變一個(gè)需求,一步一步來(lái)了解 Python 裝飾器。
首先有這么一個(gè)輸出員工打卡信息的函數(shù):
def punch(): print('昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功') punch()
輸出的結(jié)果如下:
昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功
然后,產(chǎn)品反饋,不行啊,怎么上班打卡沒(méi)有具體的日期,加上打卡的具體日期吧,這應(yīng)該很簡(jiǎn)單,分分鐘解決啦。好吧,那就直接添加打印日期的代碼吧,如下:
import time def punch(): print(time.strftime('%Y-%m-%d', time.localtime(time.time()))) print('昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功') punch()
輸出結(jié)果如下:
2018-01-09 昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功
這樣改是可以,可是這樣改是改變了函數(shù)的功能結(jié)構(gòu)的,本身這個(gè)函數(shù)定義的時(shí)候就是打印某個(gè)員工的信息和提示打卡成功,現(xiàn)在增加打印日期的代碼,可能會(huì)造成很多代碼重復(fù)的問(wèn)題。比如,還有一個(gè)地方只需要打印員工信息和打卡成功就行了,不需要日期,那么你又要重寫一個(gè)函數(shù)嗎?而且打印當(dāng)前日期的這個(gè)功能方法是經(jīng)常使用的,是可以作為公共函數(shù)給各個(gè)模塊方法調(diào)用的。當(dāng)然,這都是作為一個(gè)整體項(xiàng)目來(lái)考慮的。
既然是這樣,我們可以使用函數(shù)式編程來(lái)修改這部分的代碼。因?yàn)橥ㄟ^(guò)之前的學(xué)習(xí),我們知道 Python 函數(shù)有兩個(gè)特點(diǎn),函數(shù)也是一個(gè)對(duì)象,而且函數(shù)里可以嵌套函數(shù),那么修改一下代碼變成下面這個(gè)樣子:
import time def punch(): print('昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功') def add_time(func): print(time.strftime('%Y-%m-%d', time.localtime(time.time()))) func() add_time(punch)
輸出結(jié)果:
2018-01-09 昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功
這樣是不是發(fā)現(xiàn),這樣子就沒(méi)有改動(dòng) punch 方法,而且任何需要用到打印當(dāng)前日期的函數(shù)都可以把函數(shù)傳進(jìn) add_time 就可以了,就比如這樣:
import time def punch(): print('昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功') def add_time(func): print(time.strftime('%Y-%m-%d', time.localtime(time.time()))) func() def holiday(): print('天氣太冷,今天放假') add_time(punch) add_time(holiday)
打印結(jié)果:
2018-01-09 昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功 2018-01-09 天氣太冷,今天放假
使用函數(shù)編程是不是很方便,但是,我們每次調(diào)用的時(shí)候,我們都不得不把原來(lái)的函數(shù)作為參數(shù)傳遞進(jìn)去,還能不能有更好的實(shí)現(xiàn)方式呢?有的,就是本文要介紹的裝飾器,因?yàn)檠b飾器的寫法其實(shí)跟閉包是差不多的,不過(guò)沒(méi)有了自由變量,那么這里直接給出上面那段代碼的裝飾器寫法,來(lái)對(duì)比一下,裝飾器的寫法和函數(shù)式編程有啥不同。
import time def decorator(func): def punch(): print(time.strftime('%Y-%m-%d', time.localtime(time.time()))) func() return punch def punch(): print('昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功') f = decorator(punch) f()
輸出的結(jié)果:
2018-01-09 昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功
通過(guò)代碼,能知道裝飾器函數(shù)一般做這三件事:
接收一個(gè)函數(shù)作為參數(shù)
嵌套一個(gè)包裝函數(shù), 包裝函數(shù)會(huì)接收原函數(shù)的相同參數(shù),并執(zhí)行原函數(shù),且還會(huì)執(zhí)行附加功能
返回嵌套函數(shù)
可是,認(rèn)真一看這代碼,這裝飾器的寫法怎么比函數(shù)式編程還麻煩啊。而且看起來(lái)比較復(fù)雜,甚至有點(diǎn)多此一舉的感覺(jué)。
那是因?yàn)槲覀冞€沒(méi)有用到裝飾器的 “語(yǔ)法糖” ,我們看上面的代碼可以知道, Python 在引入裝飾器 (Decorator) 的時(shí)候,沒(méi)有引入任何新的語(yǔ)法特性,都是基于函數(shù)的語(yǔ)法特性。這也就說(shuō)明了裝飾器不是 Python 特有的,而是每個(gè)語(yǔ)言通用的一種編程思想。只不過(guò) Python 設(shè)計(jì)出了 @ 語(yǔ)法糖,讓 定義裝飾器,把裝飾器調(diào)用原函數(shù)再把結(jié)果賦值為原函數(shù)的對(duì)象名的過(guò)程變得更加簡(jiǎn)單,方便,易操作,所以 Python 裝飾器的核心可以說(shuō)就是它的語(yǔ)法糖。
那么怎么使用它的語(yǔ)法糖呢?很簡(jiǎn)單,根據(jù)上面的寫法寫完裝飾器函數(shù)后,直接在原來(lái)的函數(shù)上加 @ 和裝飾器的函數(shù)名。如下:
import time def decorator(func): def punch(): print(time.strftime('%Y-%m-%d', time.localtime(time.time()))) func() return punch @decorator def punch(): print('昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功') punch()
輸出結(jié)果:
2018-01-09 昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功
那么這就很方便了,方便在我們的調(diào)用上,比如例子中的,使用了裝飾器后,直接在原本的函數(shù)上加上裝飾器的語(yǔ)法糖就可以了,本函數(shù)也無(wú)虛任何改變,調(diào)用的地方也不需修改。
不過(guò)這里一直有個(gè)問(wèn)題,就是輸出打卡信息的是固定的,那么我們需要通過(guò)參數(shù)來(lái)傳遞,裝飾器該怎么寫呢?裝飾器中的函數(shù)可以使用 *args 可變參數(shù),可是僅僅使用 *args 是不能完全包括所有參數(shù)的情況,比如關(guān)鍵字參數(shù)就不能了,為了能兼容關(guān)鍵字參數(shù),我們還需要加上 **kwargs 。
因此,裝飾器的最終形式可以寫成這樣:
import time def decorator(func): def punch(*args, **kwargs): print(time.strftime('%Y-%m-%d', time.localtime(time.time()))) func(*args, **kwargs) return punch @decorator def punch(name, department): print('昵稱:{0} 部門:{1} 上班打卡成功'.format(name, department)) @decorator def print_args(reason, **kwargs): print(reason) print(kwargs) punch('兩點(diǎn)水', '做鴨事業(yè)部') print_args('兩點(diǎn)水', sex='男', age=99)
輸出結(jié)果如下:
2018-01-09 昵稱:兩點(diǎn)水 部門:做鴨事業(yè)部 上班打卡成功 2018-01-09 兩點(diǎn)水 {'sex': '男', 'age': 99}
至此,草根學(xué) Python 入門系列文章結(jié)束了。