Python裝飾器中常用的functools.wraps的使用
functools.wraps 是 Python 裝飾器開(kāi)發(fā)中非常關(guān)鍵、但常被忽視的工具。它不僅是代碼“優(yōu)雅性”的體現(xiàn),還直接影響調(diào)試、文檔生成、反射等功能的準(zhǔn)確性。
一、什么是functools.wraps?
functools.wraps 是一個(gè)裝飾器,它用于 將被裝飾函數(shù)的元信息(如名稱(chēng)、文檔、注解等)復(fù)制到裝飾器內(nèi)部的包裝函數(shù)上。
為什么需要它?
來(lái)看一個(gè)沒(méi)有使用 wraps 的裝飾器:
def logger(func):
def wrapper(*args, **kwargs):
print("調(diào)用函數(shù)")
return func(*args, **kwargs)
return wrapper
@logger
def add(a, b):
"""計(jì)算兩個(gè)數(shù)的和"""
return a + b
此時(shí):
print(add.__name__) # ? wrapper print(add.__doc__) # ? None
我們調(diào)用的是 add(),但其實(shí) add 是 wrapper,元信息丟失了。
二、解決方案:使用functools.wraps
from functools import wraps
def logger(func):
@wraps(func) # ?? 關(guān)鍵:復(fù)制原函數(shù)的元信息
def wrapper(*args, **kwargs):
print("調(diào)用函數(shù)")
return func(*args, **kwargs)
return wrapper
@logger
def add(a, b):
"""計(jì)算兩個(gè)數(shù)的和"""
return a + b
現(xiàn)在輸出:
print(add.__name__) # ? add print(add.__doc__) # ? 計(jì)算兩個(gè)數(shù)的和
三、底層原理分析:@wraps做了什么?
functools.wraps 是 functools.update_wrapper 的語(yǔ)法糖。
等價(jià)于:
@wraps(func) def wrapper(): ... # 等價(jià)于 def wrapper(): ... wrapper = functools.update_wrapper(wrapper, func)
update_wrapper(wrapper, func)會(huì)做這些事:
| 屬性 | 被復(fù)制 |
|---|---|
| __module__ | 模塊名 |
| __name__ | 函數(shù)名 |
| __qualname__ | 完整限定名(含類(lèi)名) |
| __annotations__ | 參數(shù)類(lèi)型注解 |
| __doc__ | 文檔字符串 |
| __dict__ | 自定義屬性字典(保持裝飾器后可追加屬性) |
四、源碼分析:wraps的定義
來(lái)自 Python 標(biāo)準(zhǔn)庫(kù) functools.py:
def wraps(wrapped,
assigned=WRAPPER_ASSIGNMENTS, # 默認(rèn):__module__, __name__, __qualname__, __annotations__, __doc__
updated=WRAPPER_UPDATES): # 默認(rèn):__dict__
def decorator(wrapper):
return update_wrapper(wrapper, wrapped, assigned, updated)
return decorator
所以 @wraps(func) 是返回了一個(gè)“裝飾器”,給你的 wrapper() 做屬性復(fù)制。
五、實(shí)用場(chǎng)景總結(jié)
| 場(chǎng)景 | 說(shuō)明 |
|---|---|
| ? 調(diào)試 | 保留原函數(shù)名,調(diào)試信息更準(zhǔn)確 |
| ? 文檔生成 | help(func)、Sphinx 等能讀取 docstring |
| ? 反射與 introspection | inspect.getfullargspec(func) 能得到正確參數(shù)信息 |
| ? functools.cache / lru_cache | 依賴(lài)函數(shù)的 __name__ 和 __hash__,否則緩存可能出錯(cuò) |
| ? unittest mock.patch | patch 也需要定位原函數(shù),名字不能丟 |
| ? IDE提示 / 自動(dòng)補(bǔ)全 | wrapper.__annotations__ 有利于類(lèi)型推斷和補(bǔ)全支持 |
六、完整示例對(duì)比
1. 單層嵌套裝飾器
? 不使用wraps
def log(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@log
def hello(name: str):
"""打招呼"""
return f"Hi, {name}"
print(hello.__name__) # wrapper
print(hello.__doc__) # None
print(hello.__annotations__) # {}
? 使用wraps
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
輸出:
hello.__name__ # hello
hello.__doc__ # 打招呼
hello.__annotations__ # {'name': <class 'str'>}
2. 多層嵌套裝飾器(以2層嵌套為例)
? 不使用wraps
def decorator1(func):
def wrapper(*args, **kwargs):
"""wrapper doc 1"""
return func(*args, **kwargs)
return wrapper
def decorator2(func):
def wrapper(*args, **kwargs):
"""wrapper doc 2"""
return func(*args, **kwargs)
return wrapper
@decorator1
@decorator2
def my_function():
"""This is the original function"""
print("Hello")
print(my_function.__name__) # wrapper
print(my_function.__doc__) # wrapper doc 1
多個(gè)裝飾器會(huì)逐層覆蓋原函數(shù)的元信息(metadata),比如 __name__、__doc__。在上述程序中,decorator2 返回一個(gè)新的函數(shù) wrapper,decorator1 再包裝這個(gè)新的函數(shù),得到另一個(gè)新的 wrapper,每一層 wrapper 都是新的函數(shù)對(duì)象,并沒(méi)有自動(dòng)保留原函數(shù)的 __name__、__doc__、__annotations__ 等屬性。
? 使用wraps
from functools import wraps
def decorator1(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""wrapper doc 1"""
return func(*args, **kwargs)
return wrapper
def decorator2(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""wrapper doc 2"""
return func(*args, **kwargs)
return wrapper
@decorator1
@decorator2
def my_function():
"""This is the original function"""
print("Hello")
print(my_function.__name__) # my_function
print(my_function.__doc__) # This is the original function
對(duì)于多層裝飾器的情況,建議每一層裝飾器都應(yīng)該加 @wraps(func),否則元信息只保留最外層那一層的,并且在多層裝飾器鏈中,調(diào)試會(huì)非?;靵y
?? 七、常見(jiàn)誤區(qū)
| 錯(cuò)誤 | 原因 |
|---|---|
| 忘記加 @wraps | 實(shí)際上丟掉了原函數(shù)元信息 |
| wraps() 用錯(cuò)了對(duì)象 | 必須傳的是“要包裝的原函數(shù)” |
| 把 wraps 當(dāng)成執(zhí)行函數(shù) | 它本身是個(gè)“返回裝飾器的函數(shù)” |
| 多層嵌套裝飾器未層層使用 wraps | 每一層都需要加 @wraps(func) |
?? 八、總結(jié)
- ? 凡是寫(xiě)裝飾器 ? wrapper 外面加 @wraps(func)
- ? 目的是:保留函數(shù)元信息,防止“身份丟失”
- ? 等價(jià)于:update_wrapper(wrapper, func)
- ? 一般配合 functools 系列使用,如:@lru_cache、@cache_property
?? 最佳實(shí)踐模板
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 執(zhí)行前邏輯
result = func(*args, **kwargs)
# 執(zhí)行后邏輯
return result
return wrapper
到此這篇關(guān)于Python裝飾器中常用的functools.wraps的使用的文章就介紹到這了,更多相關(guān)Python functools.wraps內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python?lazypredict構(gòu)建大量基本模型簡(jiǎn)化機(jī)器學(xué)習(xí)
這篇文章主要介紹了python?lazypredict構(gòu)建大量基本模型簡(jiǎn)化機(jī)器學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
python 將html轉(zhuǎn)換為pdf的幾種方法
這篇文章主要介紹了python 將html轉(zhuǎn)換為pdf的幾種方法,幫助大家更好的理解和使用python,感興趣的朋友可以了解下2020-12-12
Python實(shí)現(xiàn)快速替換Word文檔中的關(guān)鍵字
使用Python自動(dòng)化處理Word文檔可以幫助您提高效率,并減少手動(dòng)處理文檔所需的時(shí)間和精力,所以本文為大家準(zhǔn)備了Python快速替換Word文檔中的關(guān)鍵字的方法,希望對(duì)大家有所幫助2023-06-06
python和anaconda區(qū)別以及先后安裝的問(wèn)題詳解
Anaconda(開(kāi)源的Python包管理器)是一個(gè)python發(fā)行版,包含了conda、Python等180多個(gè)科學(xué)包及其依賴(lài)項(xiàng),下面這篇文章主要給大家介紹了關(guān)于python和anaconda區(qū)別以及先后安裝問(wèn)題的相關(guān)資料,需要的朋友可以參考下2022-05-05
Python NumPy實(shí)現(xiàn)數(shù)組排序與過(guò)濾示例分析講解
NumPy是Python的一種開(kāi)源的數(shù)值計(jì)算擴(kuò)展,它支持大量的維度數(shù)組與矩陣運(yùn)算,這篇文章主要介紹了使用NumPy實(shí)現(xiàn)數(shù)組排序與過(guò)濾的方法,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-05-05
python網(wǎng)絡(luò)編程之TCP通信實(shí)例和socketserver框架使用例子
這篇文章主要介紹了python網(wǎng)絡(luò)編程之TCP通信實(shí)例和socketserver框架使用例子,需要的朋友可以參考下2014-04-04

