Python 裝飾器是在面試過程高頻被問到的問題,裝飾器也是一個很是好用的特性,
熟練掌握裝飾器會讓你的編程思路更加寬廣,程序也更加 pythonic。python
今天就結合最近的世界盃帶你們理解下裝飾器。程序員
6 月 17 日德國戰墨西哥,小癡雖然是一個僞球迷,但每一年的世界盃仍是會了解下。而德國是上屆的冠軍,又是這屆奪冠熱門。德意志戰車在 32 年間小組賽就沒有輸過!臥槽!雖然小癡不多賭球,但此次德國如此強大,確定會贏吧。搏一搏單車變摩托!隨後小癡買了德國隊贏。內心想着此次確定穩了!贏了會所嫩模!小癡連比賽都不看,美滋滋的敲着代碼。面試
而後比賽結果倒是德國爆冷 0:1 輸給墨西哥隊,德國隊輸了比賽,小癡也下海乾活。只是此時的天台有點擠,風還有大。編程
小癡含淚的寫下了下面的代碼:bash
def guess_win(func):
def rooftop_status():
result = func()
print('天台已滿,請排隊!')
return result
return rooftop_status
@guess_win
def german_team():
print('德國必勝!')
複製代碼
輸出結果:函數
德國必勝!
天台已滿,請排隊!
複製代碼
首先咱們先來了解下什麼是裝飾器,嚴格來講,裝飾器只是語法糖,裝飾器是可調用的對象,能夠像常規的可調用對象那樣調用,特殊的地方是裝飾器的參數是一個函數。ui
裝飾器的存在是爲了適用兩個場景,一個是加強被裝飾函數的行爲,另外一個是代碼重用。spa
好比在上面的例子中咱們在壓德國隊贏的時候,本來的 german_team() 函數只是輸出德國必勝,但在使用裝飾器(guess_win)後,它的功能多了一項:輸出「天台已滿,請排隊!」。這就是一個簡單的裝飾器,實現了「加強被裝飾函數的行爲」。3d
一個良好的裝飾器必需要遵照兩個原則:code
1 不能修改被裝飾函數的代碼
2 不能修改被裝飾函數的調用方式
這裏並不難以理解,在如今的生產環境中,不少代碼是不能輕易的改寫,由於這樣有可能發送意想不到的影響。還有一點就是咱們在看大神的代碼,咱們根本不懂如何改寫。同時你也不能修改調用方式,由於你並不知道有在一個項目中,有多少處應用了此函數。
若是你想要很好的理解裝飾器,那下面的兩個內容須要你先有所認知。
1 函數名能夠賦值給變量
2 高階函數
咱們來看下這個例子:
def func(name):
print('我是{}!慌的一逼!'.format(name))
func('梅西')
y = func
y('勒夫')
複製代碼
輸出結果:
我是梅西!慌的一逼!
我是勒夫!慌的一逼!
複製代碼
在代碼中咱們首先定義了函數 func,並調用了 func 函數,而且把 func 賦值給 y。y = func 代表了:函數名能夠賦值給變量,而且不影響調用。
這樣講,可能還有些人不太明白。咱們在來對比下咱們經常使用的操做。這其實和整數、數字是同樣的,下面的代碼你確定熟悉:
a = 1
b = a
print(a, b)
複製代碼
2 高階函數
高階函數知足以下的兩個條件中的任意一個:a.能夠接收函數名做爲實參;b.返回值中能夠包含函數名。
在 Python 標準庫中的 map 和 filter 等函數就是高階函數。
l = [1, 2, 4]
r = map(lambda x: x*3, l)
for i in r:
print('當前天台人數:', i)
複製代碼
輸出結果:
當前天台人數: 3
當前天台人數: 6
當前天台人數: 12
複製代碼
自定義一個能返回函數的函數,也是高階函數:
def f(l):
return map(lambda x: x *5, l)
a = f(l)
for i in a:
print('當前天台人數:', i)
複製代碼
輸出結果:
當前天台人數: 5
當前天台人數: 10
當前天台人數: 20
複製代碼
如今你已經知道了「函數名賦值」和「高階函數」,有了這兩個基礎,咱們就能夠嘗試實現一個相似的裝飾器。
def status(func):
print('慌的一逼!')
return func
def name():
print('我是梅西!')
temp = status(name)
temp()
複製代碼
輸出結果:
慌的一逼!
我是梅西!
複製代碼
在這個例子中咱們定義了一個 status 函數,status 接收一個函數名而後直接返回該函數名。這樣咱們實現了不修改原函數 name,而且添加了一個新功能的需求。可是這裏有個缺陷就是函數的調用方式改變了。即不是本來的 name,而是 temp。
要解決這個問題很簡單,相信 a = a*3 這樣的表達式你們都見過,那麼上述代碼中的 temp = status(name) 一樣能夠修改成 name = status(name),這樣咱們就完美的解決了問題:既添加新功能又沒有修改原函數和其調用方式。修改後的代碼以下:
def status(func):
print('慌的一逼!')
return func
def name():
print('我是梅西!')
name = status(name)
name()
複製代碼
但這樣的代碼卻有個不便之處,即每次使用這樣的裝飾器,咱們都要寫相似 name = status(name) 的代碼。程序員都是懶的,因此纔有那麼多高級的語法。在 python 中爲了簡化這種狀況,提供了一個語法糖 @,在每一個被裝飾的函數上方使用這個語法糖就能夠省掉這一句代碼 name = status(name),最後的代碼以下:
def status(func):
print('慌的一逼!')
return func
@status
def name():
print('我是梅西!')
name()
複製代碼
這樣咱們就弄清楚了裝飾器的工做原理:
1 寫一個高階函數,即參數是函數,返回的也是函數。
2 在利用語法糖@,簡化賦值操做。
可是對比開頭的例子,仍是有些不同。在開始的例子中,咱們還實現了一個 rooftop_status 函數,來判斷下當前的天台狀是否人滿。可是咱們如今是直接返回了函數名,這樣函數調用後咱們就沒辦法作任何事情。梅西和德國慌了,咱們也慌了,各個都要天台見,但在這以前咱們也要考慮下天台的狀況。
爲了能判斷天台的狀況,因此此時咱們須要在嵌套一層函數,將實現額外功能的部分寫在內層函數中,而後將這個內層函數返回便可。這也是爲何裝飾器都是嵌套函數的緣由。
另外,開篇的例子並無返回值,也沒有參數,要對既有參數又有返回值的函數進行裝飾的話,還須要進一步完善。 可以處理返回值的裝飾器:
def guess_win(func):
def rooftop_status():
result = func()
print('天台已滿,請排隊!')
return result
return rooftop_status
@guess_win
def german_team():
print('德國必勝!')
return '贏了會所嫩模!輸了下海乾活!'
x = german_team()
print(x)
複製代碼
輸出結果:
德國必勝!
天台已滿,請排隊!
贏了會所嫩模!輸了下海乾活!
複製代碼
可以處理參數的裝飾器:
def guess_win(func):
def rooftop_status(*args, **kwargs):
result = func(*args, **kwargs)
print('天台已滿,請排隊!')
return result
return rooftop_status
@guess_win
def german_team(arg):
print('{}必勝!'.format(arg))
return '贏了會所嫩模!輸了下海乾活!'
x = german_team('德國')
y = german_team('西班牙')
print(x)
複製代碼
輸出結果:
德國必勝!
天台已滿,請排隊!
西班牙必勝!
天台已滿,請排隊!
贏了會所嫩模!輸了下海乾活!
複製代碼
裝飾器的本質是函數,其參數是另外一個函數(被裝飾的函數)。裝飾器一般會額外處理被裝飾的函數,而後把它返回,或者將其替換成另外一個函數或可調用對象。行爲良好的裝飾器能夠重用,以減小代碼量。
對於這屆的世界盃,我總結了下。
本文首發與公衆號「癡海」,後臺回覆「1024」,領取 2018 最新 python 教程。