五分鐘學會Python裝飾器,看完面試不再慌

2022-01-11 01:18:19 字數 3829 閱讀 6909

差不多五年前面試的時候,我就領教過它的重要性。那時候我python剛剛初學乍練,看完了廖雪峰大神的部落格,就去面試了。我應聘的並不是乙個python的開發崗位,但是jd當中寫到了需要熟悉python。我看網上的面經說到python經常會問裝飾器,我當時想的是裝飾器我已經看過了,應該問題不大……

沒想到面試的時候還真的問到了,面試官問我python當中的裝飾器是什麼。由於緊張和遺忘,我支支吾吾了半天也沒答上來。我隱約聽到了**那頭的一聲嘆息……

時隔多年,我已經不記得那是一家什麼公司了(估計規模也不大),但裝飾器很重要這個事情給我深深打下了烙印。

如今如果再有面試官問我python中的裝飾器是什麼,我一句話就能給回答了,倒不是我裝逼,實際上也的確只需要一句話。python中的裝飾器,本質上就是乙個高階函式

你可能不太清楚高階函式的定義,沒關係,我們可以模擬一下。在數學當中高階導數,比如二次導數,表示導數的導數。那麼這裡高階函式自然就是函式的函式,結合我們之前介紹過的函式式程式設計,也就是說是乙個返回值是函式的函式。但是這個定義是充分不必要的,也就是說裝飾器是高階函式,但是高階函式並不都是裝飾器。裝飾器是高階函式一種特殊的用法。

在介紹裝飾器的具體使用之前,我們先來了解和熟悉一下python當中的任意引數。

python當中支援任意引數,它寫成*args, **kw。表示的含義是接受任何形式的引數

舉個例子,比如我們定義乙個函式:

def

exp(a, b, c='3', d='f'):

print(a, b, c, d)

我們可以這樣呼叫:

args = [1, 3]

dt = 

exp(*args, **dt)

最後輸出的結果是1, 3, 4, 5。也就是說我們用乙個list和dict可以表示任何引數。因為python當中規定必選引數一定寫在可選引數的前面,而必選引數是可以不用加上名稱標識的,也就是可以不用寫a=1,直接傳入1即可。那麼這些沒有名稱標識的必選引數就可以用乙個list來表示,而可選引數是必須要加上名稱標識的,這些引數可以用dict來表示,這兩者相加可以表示任何形式的引數。

注意我們傳入list和dict的時候前面加上了*和**,它表示將list和dict當中的所有值展開。如果不加的話,list和dict會被當成是整體傳入。

所以如果乙個函式寫成這樣,它表示可以接受任何形式的引數。

def

exp(*args, **kw):

pass

明白了任意引數的寫法之後,裝飾器就不難了。

既然我們可以用*args, **kw接受任何引數。並且python當中支援乙個函式作為引數傳入另外乙個函式,如果我們把函式和這個函式的所有引數全部傳入另外乙個函式,那麼不就可以實現**了嗎?

還是剛才的例子,我們額外增加乙個函式:

defexp

(a, b, c='3', d='f'):

print(a, b, c, d)

defagent

(func, *args, **kwargs):

func(*args, **kwargs)

args = [1]

dt = 

agent(exp, *args, **dt)

裝飾器的本質其實就是這樣乙個agent函式,但是如果使用的時候需要手動傳入會非常麻煩,使用起來不太方便。所以python當中提供了特定的庫,我們可以讓裝飾器以註解的方式使用,大大簡化操作:

from functools import wraps

defwrapexp

(func):

def(*args, **kwargs):

defexp

(a, b, c='3', d='f'):

print(a, b, c, d)

args = [1, 3]

dt = 

exp(*args, **dt)

我們理解了裝飾器的基本使用方法之後,自然而然地會問乙個天然的問題,學會了它究竟有什麼用呢?如果你從上面的例子當中沒有領會到裝飾器的強大,不如讓我用乙個例子再來暗示一下。比如說你是乙個程式設計師,辛辛苦苦做出了乙個功能,寫了好幾千行**,上百個函式,終於通過了審核上線了。這個時候,你的產品經理找到了你說,經過分析我們發現上線的功能執行速度不達標,經常有請求超時,你能不能計算一下每個函式執行的耗時,方便我們找到需要優化的地方?

這是乙個非常合理的請求,但想想看你寫了上百個函式,如果每乙個函式都要手動新增時間計算,這要寫多少**?萬一哪個函式不小心改錯了,你又得一一檢查,並且如果要求嚴格的話你還得為每乙個函式專門寫乙個單元測試……

我想,正常的程式設計師應該都會抗拒這個需求。

但是有了裝飾器就很簡單了,我們可以實現乙個計算函式耗時的裝飾器,然後我們只需要給每乙個函式加上註解就好了。

import time

from functools import wraps

deftimethis

(func):

def(*args, **kwargs):

這也是裝飾器最大的用途,可以在不修改函式內部**的前提下,為它包裝一些額外的功能。我們之前說過裝飾器的本質是高階函式,所以我們也可以和高階函式一樣來呼叫裝飾器,比如下面這樣:

def

exp(a, b, c='3', d='f'):

print(a, b, c, d)

args = [1, 3]

dt = 

f = wrapexp(exp)

f(*args, **dt)

這樣的方式得到的結果和使用註解是一樣的,也就是說我們加上註解的本質其實就是呼叫裝飾器返回乙個新的函式。

既然和高階函式是一樣的,那麼就帶來了乙個問題,我們使用的其實已經不再是原函式了,而是乙個由裝飾器返回的新函式,雖然這個函式的功能和原函式一樣,但是一些基礎的資訊其實已經丟失了。

比如我們可以列印出函式的name來做個實驗:

正常的函式呼叫__name__返回的都是函式的名稱,但是當我們加上了裝飾器的註解之後,就會發生變化,同樣,我們輸出加上了裝飾器註解之後的結果:

有沒有什麼辦法可以保留這些函式的元資訊呢?

def

wrapexp

(func):

@wraps(func)

def(*args, **kwargs):

加上了這個註解之後,我們再來檢查函式的元資訊,會發現它和我們預期一致了。

了解了python中的裝飾器之後,再來看之前我們用過的@property, @staticmethod等註解,想必都能明白,它們背後的實現其實也是裝飾器。靈活使用裝飾器可以大大簡化我們的**,讓我們的**更加規範簡潔,還能靈活地實現一些特殊的功能。

裝飾器的用法很多,今天介紹的只是其中最基本的,在後續的文章當中,還會繼續和大家分享它更多其他的用法。在文章開始的時候我也說了,裝飾器是python高階必學的技能之一。想要熟練掌握這門語言,靈活運用,看懂大佬的原始碼,裝飾器是必須會的東西。

希望大家都能有所收穫,原創不易,厚顏求個贊和關注~

教你五分鐘學會快速排序

20為選定的關鍵字 轉換為二叉樹表示 即快排是加了關鍵字交換的二叉樹遍歷 想想快排的兄弟歸併排序,二者都是遍歷二叉樹.pub fn quicksort arr mut t where t std cmp partialord fn quick sorted arr mut t a usize,b u...

五分鐘學會markdown基本語法

在想要設定為標題的文字前面加 來表示,乙個 是一級標題,二個 是二級標題,以此類推。支援六級標題。示例 標題h1 標題h2 標題h3 標題h4 標題h5 標題h5 效果如下 標題h4 標題h5 標題h5 例項 刪除線 刪除線 開啟識別html標籤時 斜體字 斜體字 粗體 粗體 粗斜體 粗斜體 上標 ...

Python中的位運算(五分鐘學會)

python 位運算按照資料在記憶體中的二進位制位 bit 進行操作,python 位運算子只能用來操作整數型別int,它按照整數在記憶體中的二進位制形式進行計算。python 支援的位運算子如表所示。位運算子 說明使用形式 舉 例 按位與a b 4 5 按位或 a b 4 5 按位異或 a b 4...