python?自定義裝飾器使用及原理詳解(最新推薦)
注意:先行知識(shí) python,本篇文章所有代碼均為實(shí)際運(yùn)行,為原理和邏輯講解
一、裝飾器
裝飾器是 python
中的一種語(yǔ)法糖,雖然我不想用語(yǔ)法糖這個(gè)詞來(lái)表達(dá),但這句話寫(xiě)在了開(kāi)頭,我也不到用別的更準(zhǔn)確的詞來(lái)形容他了。
如果你剛接觸編程不久,不理解語(yǔ)法糖,也沒(méi)關(guān)系;在這里我說(shuō)語(yǔ)法糖并不會(huì)影響到你接下來(lái)的理解,我只是用它在贅述,作為了一種形容詞。
首先我們要搞懂裝飾器是什么東西,其次搞懂裝飾器的基礎(chǔ)知識(shí)點(diǎn),最后逐漸加深即可非常熟練的使用裝飾器。
其實(shí),你可以將裝飾器當(dāng)作是 python 自帶的函數(shù)功能增效器,使函數(shù)可以額外的增加功能,但并不影響被增加功能函數(shù)本身(雖然有辦法影響,但一般不會(huì)這樣去做)。就像是一個(gè)插座,本身這個(gè)插座只允許兩個(gè)頭的插孔電器,但這個(gè)插座有一個(gè)功能,有一個(gè)連通器可以額外增加不同的插座接口,就這樣把不同功能的插座進(jìn)行連接,這樣就實(shí)現(xiàn)了額外的功能,但這個(gè)插座本身卻不會(huì)發(fā)生改變。
二、簡(jiǎn)單的裝飾器
咱們的裝飾器學(xué)習(xí)一步步進(jìn)行,先來(lái)看一個(gè)最簡(jiǎn)單的裝飾器:
@app.route("/") def home(): return "Hello World!"
@app.route("/")
這是一個(gè)在 flask
中的路由裝飾器,在這里與 home
函數(shù)進(jìn)行綁定,在檢測(cè)到訪問(wèn)當(dāng)前站點(diǎn)的根目錄時(shí),即會(huì)執(zhí)行 home
函數(shù)。
而在這里,裝飾器的作用則是給了當(dāng)前的 home
函數(shù)一個(gè)額外的功能,使其能夠響應(yīng) web
請(qǐng)求。
那么咱們開(kāi)始編寫(xiě)一個(gè)自定義裝飾器:
def simple_decorator(func): def wrapper(): print("Before function call") result = func() print("After function call") return result return wrapper
以上是一個(gè)簡(jiǎn)單的自定義裝飾器的實(shí)現(xiàn),咱們可以得知,一個(gè)簡(jiǎn)單的嵌套函數(shù)形式。咱們解構(gòu)一下裝飾器的語(yǔ)法,得到以下結(jié)果:
def 裝飾器名稱(chēng)(func): # 接收一個(gè)函數(shù)(比如 home) def wrapper(): # 包裝原函數(shù) # 添加自定義的新功能(比如權(quán)限檢查) func() # 執(zhí)行原函數(shù) return wrapper
我們從以上的語(yǔ)法中得知,裝飾器最外層的函數(shù),為一個(gè)接收一個(gè)函數(shù)作為參數(shù)的函數(shù),其內(nèi)部用一個(gè) wrapper 為名函數(shù)包裝原函數(shù) func(),并且在此處 func() 執(zhí)行原函數(shù);最后返回整個(gè)函數(shù) wrapper。
你可以理解為一個(gè)函數(shù)使用了裝飾器如下:
@decorator def func(): pass
隨后會(huì)轉(zhuǎn)變成:
func = decorator(func)
你再這里可能有疑問(wèn),在這里一定要使用 wrapper 來(lái)包裹(包裝)原函數(shù) func() 嗎?當(dāng)然不,在這里 wrapper 只是一個(gè)約定俗成的名字,但推薦用 wrapper 為名的函數(shù)來(lái)包裹 func() 原函數(shù)的執(zhí)行,因?yàn)榭赡苣承┑谌降墓ぞ?、?kù) 之類(lèi)會(huì)采用這個(gè)約定俗稱(chēng)的方式作為依賴(lài),說(shuō)不定會(huì)影響到某些功能的實(shí)現(xiàn),并且 func 也是約定俗稱(chēng)代表著原函數(shù)。
其實(shí)通過(guò)以上的簡(jiǎn)單了解,此時(shí)應(yīng)該知曉,基礎(chǔ)的自定義裝飾器其實(shí)就是一個(gè)二次包裹的一個(gè)函數(shù),外層函數(shù)接收原函數(shù)為參數(shù),二層函數(shù)則包裹這個(gè)原函數(shù)進(jìn)行執(zhí)行,最后返回包裹原函數(shù)的函數(shù)即可。
三、裝飾器詳解
通過(guò)第二點(diǎn)我們可以發(fā)現(xiàn),例子中沒(méi)有對(duì)應(yīng)的傳遞參數(shù)給裝飾器的功能,那當(dāng)我們需要對(duì)裝飾器進(jìn)行傳參時(shí)該怎么做呢?
其實(shí)很簡(jiǎn)單,咱們只需要在原本的兩層包裹的函數(shù)外再加上一層函數(shù)接收參數(shù)即可:
def out_decorator(param): def in_decorator(func): # 第二層:接收函數(shù) def wrapper(): # 第三層:包裝函數(shù) print(param) func() return wrapper return in_decorator @out_decorator("裝飾器傳參") # 傳遞參數(shù) def home(): print("原本函數(shù)內(nèi)容") home() # 輸出:裝飾器傳參 → 原本函數(shù)內(nèi)容
再上面的函數(shù)中,out_decorator 是一個(gè)新增接收參數(shù)的裝飾器,當(dāng)然也可以接收多個(gè),其內(nèi)部的兩層包裹的函數(shù)與之前第二次所闡述的簡(jiǎn)單裝飾器一樣,使用 in_decorator 接收了 func 原函數(shù)后,在 in_decorator 函數(shù)內(nèi)部再使用 wrapper 對(duì)原函數(shù)進(jìn)行包裹即可,最后返回包裹函數(shù) wrapper。
四、原函數(shù)的傳參
既然說(shuō)到了裝飾器傳參,那原函數(shù)的傳參我們?cè)撛趺醋瞿兀?/p>
在此我們使用一種無(wú)需維護(hù)的傳參方式,為了使一些可能對(duì)python 接觸不深的同學(xué)閱讀,在此回對(duì)這種方式進(jìn)行一下解釋。
4.1 位置參數(shù)
在正式介紹傳參前,需要了解一下函數(shù)參數(shù)的兩種參數(shù)寫(xiě)法,首先是位置參數(shù)。
咱們?cè)趯?duì)函數(shù)進(jìn)行傳參時(shí)可能會(huì)這樣 func(1, 2, 3)
;此時(shí)我們將 func 的參數(shù) 1 會(huì)說(shuō)成傳給 func 的第一個(gè)參數(shù)為 1,那么第二個(gè)參數(shù)為2,第三個(gè)參數(shù)為 3… 這樣以此類(lèi)推,我們使用位置對(duì)這些參數(shù)進(jìn)行語(yǔ)言上的引導(dǎo)與定位,這種傳參方式的參數(shù)即為位置參數(shù)。
4.2 關(guān)鍵字參數(shù)
在傳參時(shí)不僅可以使用 func(1, 2, 3)
這樣的位置參數(shù),還可以使用 func(name="John", age=20)
這樣的傳參方式。
func(name="John", age=20)
的傳參方式指定了參數(shù)所對(duì)應(yīng)的“名”,就例如給與了一個(gè)鍵值對(duì)的方式,這種方式不再使用位置信息作為引導(dǎo)指定,我們會(huì)說(shuō)給與 func 函數(shù)的參數(shù)有 2 個(gè),一個(gè)是name 另一個(gè)是 age,name 的值為 john,age 的值為 20;這種傳參方式的參數(shù)即為關(guān)鍵字參數(shù)。
4.3 *args 與 ?**kwargs 的打包
在python 中,有兩個(gè)特殊的符號(hào)表示不同參數(shù),其中 *args 表示位置參數(shù),**kwargs 表示 關(guān)鍵字參數(shù)。
在這里,若讀者沒(méi)有深究,可能覺(jué)得 *args 與 ?**kwargs 就是關(guān)鍵字的存在,其實(shí),在這里真正起作用的是 * 和 **,而 args 、kwargs 則是約定俗稱(chēng)的名稱(chēng),args 表示位置參數(shù)、kwargs 表示關(guān)鍵字參數(shù)(先這樣理解,但在不同情況下有其他解釋?zhuān)@只是當(dāng)說(shuō)命名上。)
在 python 中 * 與 ** 為python中的可變參數(shù),主要用于對(duì)數(shù)據(jù)進(jìn)行打包、解包。
我們先演示打包吧,以下是一個(gè)例子:
def func(a, *args, ?**kwargs): print("a:", a) print("args:", args) # 額外位置參數(shù) → 元組 print("kwargs:", kwargs) # 關(guān)鍵字參數(shù) → 字典
當(dāng)我們傳入?yún)?shù) func(1, 2, 3, name=“John”, age=20) 時(shí),位置參數(shù)1則是個(gè)與到 func 中的參數(shù) a,2與3則直接被 *args進(jìn)行了打包,成為一個(gè)元組 (2, 3) 給與到變量 args,而 name=“John”, age=20 關(guān)鍵字變量,則被 **kwargs 打包給與到 kwargs。
其結(jié)果輸出為:
# 輸出:
# a: 1
# args: (2, 3)
# kwargs: {'name': 'John', 'age': 20}
其中參數(shù) a, *args, ?**kwargs :
- *args 是一個(gè) * 號(hào)則表示把傳入過(guò)來(lái)的位置參數(shù)打包成元組給與到自身
- **kwargs 是兩個(gè) *號(hào)則表示 把傳入過(guò)來(lái)的關(guān)鍵字參數(shù)打包成字典給與到自身
不要認(rèn)為這是指針,這是 python 中的可變參數(shù)的語(yǔ)法*args 和 **kwargs 只是參數(shù)處理方式,不涉及內(nèi)存地址操作。python是高級(jí)語(yǔ)言,所有變量都是對(duì)對(duì)象的引用?(類(lèi)似指針的抽象概念),但用戶無(wú)法直接操作內(nèi)存地址。
總結(jié)一下:
- 在函數(shù)定義中 * 和 ** 用于接收任意數(shù)量的參數(shù)進(jìn)行打包接收
4.4 *args 與 ?**kwargs 的解包
以下是一個(gè)演示 * 和 ** 的解包代碼:
def add(a, b): return a + b numbers = (1, 2) add(*numbers) # 等價(jià)于 add(1, 2) → 3 params = {"a": 3, "b": 4} add(**params) # 等價(jià)于 add(a=3, b=4) → 7
以上代碼在對(duì)元組 numbers = (1, 2) 進(jìn)行解包后 add(*numbers) 傳遞參數(shù),此時(shí)的元組參數(shù)變成了單獨(dú)的數(shù)據(jù)進(jìn)行傳參,此時(shí)參數(shù)為位置參數(shù)。
以上代碼在對(duì)字典 params = {“a”: 3, “b”: 4} 進(jìn)行解包后 add(**params) 傳遞參數(shù),此時(shí)的元組參數(shù)變成了單獨(dú)的key、val 數(shù)據(jù)進(jìn)行傳參,此時(shí)參數(shù)為關(guān)鍵字參數(shù)參數(shù)。
總結(jié)一下:
- 在函數(shù)調(diào)用中 * 和 ** 用于解包參數(shù)(將序列或字典展開(kāi)為單獨(dú)參數(shù))
4.5 原函數(shù)傳參實(shí)現(xiàn)
通過(guò)以上對(duì) *args 與 ?kwargs 后,已經(jīng)知曉如何接收所有的位置參數(shù)與關(guān)鍵字參數(shù),那么此時(shí)我們只需要在包裝原函數(shù)的 wrapper 函數(shù)寫(xiě)上 *args, ?kwargs,再原樣給與到 func 函數(shù)參數(shù)即可:
def decorator(func): def wrapper(*args, ?**kwargs): # 接收所有參數(shù) print("裝飾器邏輯") return func(*args, ?**kwargs) # 原樣傳遞參數(shù)給原函數(shù) return wrapper
可能有些同學(xué)會(huì)問(wèn),不給 wrapper 寫(xiě) *args, ?**kwargs 不可以嗎?其實(shí)裝飾器的核心是替換原函數(shù),當(dāng)調(diào)用被裝飾的函數(shù)時(shí),實(shí)際上調(diào)用的是wrapper函數(shù)。
因此,wrapper需要能夠接收所有傳遞給原函數(shù)的參數(shù),無(wú)論是位置參數(shù)還是關(guān)鍵字參數(shù)。如果wrapper不接受任何參數(shù),當(dāng)被裝飾的函數(shù)被調(diào)用時(shí),傳遞的參數(shù)就會(huì)丟失,導(dǎo)致錯(cuò)誤。
若你的裝飾器也需要接收參數(shù),那么再嵌套一層函數(shù)即可:
def out_decorator(param): def in_decorator(func): def wrapper(*args, ?**kwargs): # 接收所有參數(shù) print("裝飾器邏輯") return func(*args, ?**kwargs) # 原樣傳遞參數(shù)給原函數(shù) return wrapper return in_decorator
4.6 保留原函數(shù)元數(shù)據(jù)
若有一個(gè)函數(shù)使用了裝飾器:
def decorator(func): def wrapper(*args, ?**kwargs): return func(*args, ?**kwargs) return wrapper @decorator def home(): print("Hello") print(home.__name__) # 輸出:wrapper
此時(shí)當(dāng)你調(diào)用當(dāng)前 home 函數(shù)的元數(shù)據(jù)函數(shù)名時(shí),會(huì)直接輸出 wrapper 這個(gè)包裹原函數(shù)的函數(shù)名。
在上一節(jié)說(shuō)過(guò)裝飾器的核心是替換原函數(shù),當(dāng)調(diào)用被裝飾的函數(shù)時(shí),實(shí)際上調(diào)用的是wrapper函數(shù)。
那么如何才能讓裝飾器不干擾原函數(shù),使其假裝起到“裝飾”作用,假裝不更改其內(nèi)部元數(shù)據(jù)呢?此時(shí)只需要添加 @wraps(func) 即可。
注意 @wraps(func) 需要引入 from functools import wraps。
修改代碼為:
from functools import wraps def decorator(func): @wraps(func) def wrapper(*args, ?**kwargs): return func(*args, ?**kwargs) return wrapper @decorator def home(): print("Hello") print(home.__name__) # 輸出:wrapper
如果是裝飾器需要接收參數(shù),也同理添加即可在 wrapper 之前即可:
def out_decorator(param): def in_decorator(func): @wraps(func) def wrapper(*args, ?**kwargs): # 接收所有參數(shù) print("裝飾器邏輯") return func(*args, ?**kwargs) # 原樣傳遞參數(shù)給原函數(shù) return wrapper return in_decorator
在這里建議,除非你有特殊需求,其他情況建議都加上 @wraps(func)。
五、類(lèi)裝飾器
裝飾器不止可以使用函數(shù)實(shí)現(xiàn),還可以使用類(lèi),代碼如下:
class ClassDecorator: def __init__(self, func): self.func = func def __call__(self, *args, ?**kwargs): print("裝飾器邏輯前") result = self.func(*args, ?**kwargs) print("裝飾器邏輯后") return result @ClassDecorator def my_function(): print("原函數(shù)邏輯") my_function() # 輸出: # 裝飾器邏輯前 # 原函數(shù)邏輯 # 裝飾器邏輯后
__call__
方法承擔(dān)了類(lèi)似 wrapper 的角色,在 Python 中,__call__
是一個(gè)特殊方法,它允許類(lèi)的實(shí)例像函數(shù)一樣被調(diào)用,從而執(zhí)行 __call__
中的代碼,這與之前所說(shuō)的在調(diào)用被裝飾的函數(shù)實(shí)際上是執(zhí)行了 wrapper 一樣,則當(dāng)類(lèi)裝飾器中使用 __call__
時(shí),它的核心作用是讓裝飾后的函數(shù)調(diào)用邏輯被包裝在類(lèi)的方法中。
到此這篇關(guān)于python 自定義裝飾器使用及原理詳解的文章就介紹到這了,更多相關(guān)python 自定義裝飾器使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python中for循環(huán)和while循環(huán)的基本使用方法
這篇文章主要介紹了Python中for循環(huán)和while循環(huán)的基本使用方法,是Python入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-08-08使用pandas中的DataFrame.rolling方法查看時(shí)間序列中的異常值
Pandas是Python中最受歡迎的數(shù)據(jù)分析和處理庫(kù)之一,提供了許多強(qiáng)大且靈活的數(shù)據(jù)操作工具,在Pandas中,DataFrame.rolling方法是一個(gè)強(qiáng)大的工具,在本文中,我們將深入探討DataFrame.rolling方法的各種參數(shù)和示例,以幫助您更好地理解和應(yīng)用這個(gè)功能2023-12-12

Python中用altzone()方法處理時(shí)區(qū)的教程

python對(duì)csv文件追加寫(xiě)入列的方法

selenium2.0中常用的python函數(shù)匯總