Python裝飾器基于函數(shù)的使用與定義

Python 裝飾器是 Python 中強大的函數(shù)式編程特性之一撕蔼。它可以讓我們在不修改函數(shù)源代碼的情況下灯萍,增強函數(shù)的功能蕉鸳。Python 裝飾器的實現(xiàn)方式非常靈活骇扇,可以處理任何類型的函數(shù)映砖,也可以嵌套多個裝飾器间坐。

在本文中,我們將深入探討 Python 裝飾器邑退,包括裝飾器的基礎(chǔ)用法和使用過程中應(yīng)注意的問題竹宋,以及如何自定義 Python 裝飾器和自定義裝飾器的注意事項。

裝飾器的基本語法
Python 裝飾器的基本語法如下所示:

@decoator
def func():
pass
其中地技,decorator 是裝飾器函數(shù)蜈七,func() 是被裝飾的函數(shù)。Python 裝飾器的核心思想是莫矗,將被裝飾的函數(shù)作為參數(shù)傳遞給裝飾器函數(shù)飒硅,然后在裝飾器函數(shù)內(nèi)部創(chuàng)建一個新函數(shù)砂缩,將原函數(shù)替換為新函數(shù)。裝飾器函數(shù)可以在新函數(shù)中添加額外的功能三娩,同時也可以保留原函數(shù)的功能庵芭。

例如,下面的代碼演示了一個簡單的裝飾器雀监,用于在函數(shù)調(diào)用前后打印一些日志:

def log(func):
def wrapper(*args, *kwargs):
print(f"Calling function {func.name} with args: {args}, kwargs: {kwargs}")
result = func(
args, **kwargs)
print(f"Function {func.name} returned: {result}")
return result
return wrapper

@log
def add(a, b):
return a + b

result = add(1, 2)
print(result) # 輸出 3
在上面的代碼中双吆,log() 函數(shù)是一個裝飾器函數(shù),它接收一個函數(shù)作為參數(shù)会前,并返回一個新的函數(shù) wrapper()好乐。wrapper() 函數(shù)包裝了原函數(shù) add(),在函數(shù)調(diào)用前后打印日志瓦宜,并最終返回原函數(shù)的執(zhí)行結(jié)果曹宴。

裝飾器的注意事項
在使用 Python 裝飾器的過程中,需要注意以下幾點:

裝飾器函數(shù)必須返回一個函數(shù)歉提,否則會導(dǎo)致語法錯誤笛坦。
裝飾器函數(shù)應(yīng)該使用 *args 和 **kwargs 參數(shù),以便于可以接收任意數(shù)量的參數(shù)苔巨,并傳遞給被裝飾的函數(shù)版扩。
裝飾器函數(shù)應(yīng)該使用 functools.wraps() 來保留被裝飾函數(shù)的元信息,例如函數(shù)名侄泽、文檔字符串等礁芦。
例如,下面的代碼演示了如何使用 functools.wraps() 保留被裝飾函數(shù)的元信息:

import functools

def log(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
print(f"Calling function {func.name} with args: {args}, kwargs: {kwargs}")
result = func(
args, **kwargs)
print(f"Function {func.name} returned: {result}")
return result
return wrapper

@log
def add(a, b):
return a + b

result = add(1, 2)
print(result) # 輸出 3
裝飾器的嵌套定義
裝飾器可以嵌套定義悼尾,但是過度的嵌套可能會導(dǎo)致代碼難以理解和維護柿扣。
例如,下面的代碼演示了如何使用裝飾器嵌套實現(xiàn)多個功能:

def log(msg):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
print(f"{msg}: Calling function {func.name} with args: {args}, kwargs: {kwargs}")
result = func(
args, **kwargs)
print(f"{msg}: Function {func.name} returned: {result}")
return result
return wrapper
return decorator

@log("INFO")
@log("DEBUG")
def add(a, b):
return a + b

result = add(1, 2)
print(result) # 輸出 3
在上面的代碼中闺魏,使用了兩個裝飾器函數(shù) log() 和 decorator()未状,它們分別負責打印日志和添加額外的功能。log() 函數(shù)接收一個字符串參數(shù)析桥,用于指定日志級別司草。decorator() 函數(shù)接收一個函數(shù)參數(shù),用于包裝原函數(shù)泡仗。add() 函數(shù)被兩個裝飾器函數(shù)嵌套使用埋虹,分別添加了兩種不同的日志級別和額外的功能。

裝飾器嵌套定義需要注意一點: 在嵌套過程中娩怎,返回的最內(nèi)層函數(shù)搔课,必須是一個和被裝飾器函數(shù)參數(shù)具有一致性兼容的包裹函數(shù)(對與這點需正確理解args和*kwargs的使用),并且返回該層嵌套的函數(shù)必須是一個接收一個函數(shù)作為參數(shù)的函數(shù)截亦。也就是嵌套定義的裝飾器爬泥,必須保證裝飾器最內(nèi)層滿足裝飾器基本結(jié)構(gòu)旦事。

雖說裝飾器定義的嵌套過度,會導(dǎo)致的代碼維護變得困難急灭,但另一方面姐浮,裝飾器的嵌套定義,在業(yè)務(wù)上葬馋,為我們實現(xiàn)修飾函數(shù)不需要卖鲤,但在裝飾器層級維護需要的參數(shù)傳入提供了支撐,這也為我們業(yè)務(wù)上實現(xiàn)裝飾器的靈活性提供了支撐畴嘶。正如上述例子的log(msg)中的msg參數(shù)蛋逾,僅是為了日志的維護,其并不參與add()方法的運算邏輯窗悯。同樣的区匣,這也就為我們實現(xiàn)類似如Flask中,定義的route這種行為提供了支撐(當然蒋院,在Flask中的route實現(xiàn)邏輯會更復(fù)雜亏钩,而且Flask中的裝飾器是針對對象存在,對與Flask中的裝飾器在對象中的使用欺旧,待后續(xù)專們的文章來討論)姑丑。

多個裝飾器的執(zhí)行順序
多個作用于同一個方法上,裝飾器的執(zhí)行順序是從下往上辞友,即從最后一個裝飾器開始執(zhí)行栅哀。
例如,下面的代碼演示了裝飾器的執(zhí)行順序:

def log1(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
print("Calling function log1")
result = func(
args, **kwargs)
print("Function log1 returned")
return result
return wrapper

def log2(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
print("Calling function log2")
result = func(
args, **kwargs)
print("Function log2 returned")
return result
return wrapper

@log1
@log2
def add(a, b):
return a + b

result = add(1, 2)
print(result) # 輸出 3
在上面的代碼中称龙,add() 函數(shù)被兩個裝飾器函數(shù) log1() 和 log2() 嵌套使用留拾。由于裝飾器的執(zhí)行順序是從下往上,因此先執(zhí)行 log2() 裝飾器鲫尊,再執(zhí)行 log1() 裝飾器痴柔。

自定義 Python 裝飾器
除了使用 Python 內(nèi)置的裝飾器函數(shù),我們還可以自定義裝飾器函數(shù)马昨。自定義裝飾器可以根據(jù)應(yīng)用場景來實現(xiàn)各種不同的功能竞帽。

自定義裝飾器的基本語法
自定義裝飾器的基本語法如下所示:

def decorator(func):
def wrapper(*args, *kwargs):
# 在函數(shù)調(diào)用前執(zhí)行一些操作
result = func(
args, **kwargs)
# 在函數(shù)調(diào)用后執(zhí)行一些操作
return result
return wrapper

其中,decorator() 是裝飾器函數(shù)鸿捧,它接收一個函數(shù)作為參數(shù),并返回一個新的函數(shù) wrapper()疙渣。在 wrapper() 函數(shù)內(nèi)部匙奴,首先可以執(zhí)行一些操作,例如打印日志妄荔、檢查參數(shù)等泼菌,然后再調(diào)用被裝飾的函數(shù)谍肤,并在函數(shù)調(diào)用后執(zhí)行一些操作。

例如哗伯,下面的代碼演示了一個自定義裝飾器荒揣,用于計算函數(shù)執(zhí)行時間:

import time

def timer(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
start_time = time.time()
result = func(
args, **kwargs)
end_time = time.time()
print(f"Function {func.name} took {end_time - start_time:.4f} seconds to run")
return result
return wrapper

@timer
def add(a, b):
return a + b

result = add(1, 2)
print(result) # 輸出 3
在上面的代碼中,timer() 函數(shù)是一個自定義裝飾器函數(shù)焊刹,它接收一個函數(shù)作為參數(shù)系任,并返回一個新的函數(shù) wrapper()。在 wrapper() 函數(shù)內(nèi)部虐块,首先記錄函數(shù)執(zhí)行的開始時間 start_time俩滥,然后調(diào)用被裝飾的函數(shù) func,并記錄函數(shù)執(zhí)行的結(jié)束時間 end_time贺奠。最后霜旧,打印函數(shù)執(zhí)行時間,并返回原函數(shù)的執(zhí)行結(jié)果儡率。

自定義裝飾器的注意事項
在自定義 Python 裝飾器的過程中挂据,需要注意以下幾點:

裝飾器函數(shù)必須返回一個函數(shù),否則會導(dǎo)致語法錯誤儿普。

裝飾器函數(shù)應(yīng)該使用 *args 和 **kwargs 參數(shù)棱貌,以便于可以接收任意數(shù)量的參數(shù),并傳遞給被裝飾的函數(shù)箕肃。

裝飾器函數(shù)應(yīng)該使用 functools.wraps() 來保留被裝飾函數(shù)的元信息婚脱,例如函數(shù)名、文檔字符串等勺像。

自定義裝飾器的功能應(yīng)該明確障贸、簡單,不應(yīng)該過于復(fù)雜吟宦。

自定義裝飾器應(yīng)該遵守 Python 裝飾器的基本語法和注意事項篮洁。

例如,下面的代碼演示了一個自定義裝飾器殃姓,用于檢查函數(shù)的參數(shù)類型:

def check_types(types):
def decorator(func):
@functools.wraps(func)
def wrapper(
args, *kwargs):
for arg, arg_type in zip(args, types):
if not isinstance(arg, arg_type):
raise TypeError(f"{arg} is not of type {arg_type}")
for arg_name, arg_type in kwargs.items():
if arg_name in func.code.co_varnames and not isinstance(kwargs[arg_name], arg_type):
raise TypeError(f"{arg_name}={kwargs[arg_name]} is not of type {arg_type}")
return func(
args, **kwargs)
return wrapper
return decorator

@check_types(int, int)
def add(a, b):
return a + b

result = add(1, '2')
print(result) # 拋出 TypeError 異常
在上面的代碼中袁波,check_types() 函數(shù)是一個自定義裝飾器函數(shù),它接收一個或多個參數(shù)類型作為裝飾器的參數(shù)蜗侈,并返回一個新的裝飾器函數(shù) decorator()篷牌。在 decorator() 函數(shù)內(nèi)部,使用 *args 和 **kwargs 參數(shù)踏幻,遍歷函數(shù)的位置參數(shù)和關(guān)鍵字參數(shù)枷颊,檢查它們的類型是否與指定的參數(shù)類型相符。如果參數(shù)類型不符,就拋出 TypeError 異常夭苗。

在上面的代碼中信卡,add() 函數(shù)被 check_types() 裝飾器裝飾,指定了兩個參數(shù)類型為 int题造。由于第二個參數(shù)類型不符合要求傍菇,因此函數(shù)調(diào)用拋出了 TypeError 異常。

總結(jié)
Python 裝飾器是 Python 中強大的函數(shù)式編程特性之一界赔。它可以讓我們在不修改函數(shù)源代碼的情況下丢习,增強函數(shù)的功能。Python 裝飾器的實現(xiàn)方式非常靈活仔蝌,可以處理任何類型的函數(shù)泛领,也可以嵌套多個裝飾器。

在本文中敛惊,我們深入探討了 Python 裝飾器的基礎(chǔ)用法和使用過程中應(yīng)注意的問題渊鞋,以及如何自定義 Python 裝飾器和自定義裝飾器的注意事項。通過學(xué)習(xí)本文瞧挤,相信你已經(jīng)了解了 Python 裝飾器的原理和使用方法锡宋,并可以根據(jù)實際需求自定義各種不同的裝飾器。

當然特恬,本文只是對 Python 裝飾器的簡單介紹执俩,實際上 Python 裝飾器的應(yīng)用非常廣泛。例如癌刽,Python 中的 Flask 框架就廣泛使用了裝飾器役首,用于處理 HTTP 請求和響應(yīng)。如果你想深入了解 Python 裝飾器显拜,建議參考 Python 官方文檔和相關(guān)書籍衡奥,以及實踐中遇到的問題,并結(jié)合實際場景進行學(xué)習(xí)和探索远荠。

最后矮固,值得注意的是,雖然 Python 裝飾器非常強大和靈活譬淳,但是過度的使用裝飾器可能會導(dǎo)致代碼難以理解和維護档址。因此,在使用 Python 裝飾器時邻梆,需要謹慎考慮其實際意義和影響守伸,并盡量保持代碼的簡潔和可讀性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末确虱,一起剝皮案震驚了整個濱河市含友,隨后出現(xiàn)的幾起案子替裆,更是在濱河造成了極大的恐慌校辩,老刑警劉巖窘问,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宜咒,居然都是意外死亡惠赫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門故黑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來儿咱,“玉大人,你說我怎么就攤上這事场晶』觳海” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵诗轻,是天一觀的道長钳宪。 經(jīng)常有香客問我,道長扳炬,這世上最難降的妖魔是什么吏颖? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮恨樟,結(jié)果婚禮上半醉,老公的妹妹穿的比我還像新娘。我一直安慰自己劝术,他們只是感情好缩多,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著养晋,像睡著了一般衬吆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上匙握,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天咆槽,我揣著相機與錄音,去河邊找鬼圈纺。 笑死秦忿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蛾娶。 我是一名探鬼主播灯谣,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蛔琅!你這毒婦竟也來了胎许?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辜窑,沒想到半個月后钩述,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡穆碎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年牙勘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片所禀。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡方面,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出色徘,到底是詐尸還是另有隱情恭金,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布褂策,位于F島的核電站横腿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏辙培。R本人自食惡果不足惜蔑水,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扬蕊。 院中可真熱鬧搀别,春花似錦、人聲如沸尾抑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽再愈。三九已至榜苫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間翎冲,已是汗流浹背垂睬。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抗悍,地道東北人驹饺。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像缴渊,于是被迫代替她去往敵國和親赏壹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容