用世界盃的方式,讓你理解 Python 裝飾器,有點長!

Python 裝飾器是在面試過程高頻被問到的問題,

裝飾器也是一個非常好用的特性,

熟練掌握裝飾器會讓你的編程思路更加寬廣,

程序也更加 pythonic。

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

今天我們就結合世界盃來帶大家理解裝飾器

德國戰車

6 月 17 日德國戰墨西哥

小痴雖然是一個偽球迷,但每年的世界盃還是會了解下。

而德國是上屆的冠軍,又是這屆奪冠熱門。

德意志戰車在 32 年間小組賽就沒有輸過!

臥槽!

雖然小痴很少賭球,但這次德國如此強大,

肯定會贏吧。

搏一搏單車變摩托!

隨後小痴買了德國隊贏。

心裡想著這次肯定穩了!贏了會所嫩模!

小痴連比賽都不看,

美滋滋的敲著代碼。

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

然後比賽結果卻是德國爆冷 0:1 輸給墨西哥隊,

德國隊輸了比賽

只是此時的天台有點擠,

風還有大。

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

含淚的寫下了下面的代碼:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

輸出結果:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

裝飾器是什麼

首先我們先來了解下什麼是裝飾器,

嚴格來說,

裝飾器只是語法糖,裝飾器是可調用的對象,

可以像常規的可調用對象那樣調用,

特殊的地方是裝飾器的參數是一個函數。

裝飾器的存在是為了適用兩個場景,

一個是增強被裝飾函數的行為,

另一個是代碼重用。

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

比如在上面的例子中我們在壓德國隊贏的時候,

原本的 german_team() 函數只是輸出德國必勝,

但在使用裝飾器(guess_win)後,

它的功能多了一項:

輸出「天台已滿,請排隊!」。

這就是一個簡單的裝飾器,實現了「增強被裝飾函數的行為」。

一個良好的裝飾器必須要遵守兩個原則:

  • 不能修改被裝飾函數的代碼
  • 不能修改被裝飾函數的調用方式

這裡並不難以理解,

在現在的生產環境中,很多代碼是不能輕易的改寫,

因為這樣有可能發送意想不到的影響。

還有一點就是我們在看大神的代碼,

我們根本不懂如何改寫。

同時你也不能修改調用方式,

因為你並不知道有在一個項目中,

有多少處應用了此函數。

裝飾器理解基礎

如果你想要很好的理解裝飾器,

那下面的兩個內容需要你先有所認知:

  • 函數名可以賦值給變量
  • 高階函數

1. 函數名可以賦值給變量

我們來看下這個例子:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

輸出結果:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

在代碼中我們首先定義了函數 func,

並調用了 func 函數,並且把 func 賦值給 y。

y = func 表明了:

函數名可以賦值給變量,並且不影響調用。

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

這樣講,

可能還有些人不太明白。

我們在來對比下我們常用的操作。

這其實和整數、數字是一樣的,

下面的代碼你肯定熟悉:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

2 .高階函數

高階函數滿足如下的兩個條件中的任意一個:

a.可以接收函數名作為實參;

b.返回值中可以包含函數名。

在 Python 標準庫中的 map 和 filter 等函數就是高階函數。

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

輸出結果:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

自定義一個能返回函數的函數,

也是高階函數:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

輸出結果:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

實現一個類似的裝飾器

現在你已經知道了「函數名賦值」和「高階函數」,

有了這兩個基礎,

我們就可以嘗試實現一個類似的裝飾器。

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

輸出結果:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

在這個例子中我們定義了一個 status 函數,

status 接收一個函數名然後直接返回該函數名。

這樣我們實現了不修改原函數 name,

並且添加了一個新功能的需求。

但是這裡有個缺陷就是函數的調用方式改變了。

即不是原本的 name,而是 temp。

要解決這個問題很簡單,

相信 a = a*3 這樣的表達式大家都見過,

那麼上述代碼中的 temp = status(name) 同樣可以修改為 name = status(name),

這樣我們就完美的解決了問題:

既添加新功能又沒有修改原函數和其調用方式。

修改後的代碼如下:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

但這樣的代碼卻有個不便之處,即每次使用這樣的裝飾器,

我們都要寫類似 name = status(name) 的代碼。

程序員都是懶的,

所以才有那麼多高級的語法。

在 python 中為了簡化這種情況,

提供了一個語法糖 @,

在每個被裝飾的函數上方使用這個語法糖就可以省掉這一句代碼 name = status(name),

最後的代碼如下:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

這樣我們就弄清楚了裝飾器的工作原理:

  • 寫一個高階函數,即參數是函數,返回的也是函數。
  • 在利用語法糖@,簡化賦值操作。

但是對比開頭的例子,還是有些不一樣。

在開始的例子中,

我們還實現了一個 rooftop_status 函數,

來判斷下當前的天台狀是否人滿。

但是我們現在是直接返回了函數名,

這樣函數調用後我們就沒辦法做任何事情。

梅西和德國慌了,我們也慌了,

各個都要天台見,

但在這之前我們也要考慮下天台的情況。

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

為了能判斷天台的情況,所以此時我們需要在嵌套一層函數,

將實現額外功能的部分寫在內層函數中,

然後將這個內層函數返回即可。

這也是為什麼裝飾器都是嵌套函數的原因。

另外,

開篇的例子並沒有返回值,也沒有參數,

要對既有參數又有返回值的函數進行裝飾的話,

還需要進一步完善。

能夠處理返回值的裝飾器:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

輸出結果:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

能夠處理參數的裝飾器:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

輸出結果:

用世界盃的方式,讓你理解 Python 裝飾器,有點長!

總結

裝飾器的本質是函數,其參數是另一個函數(被裝飾的函數)。

裝飾器通常會額外處理被裝飾的函數,

然後把它返回,

或者將其替換成另一個函數或可調用對象。

行為良好的裝飾器可以重用,以減少代碼量。

對於這屆的世界盃,我總結了下。

python小白可以私信我回復【01】有基礎教程

用世界盃的方式,讓你理解 Python 裝飾器,有點長!


分享到:


相關文章: