python @wraps()裝飾器的使用
“它讓裝飾器不‘穿幫’,被裝飾的函數(shù)還能假裝自己是原來的函數(shù)。”
下面從 0 到源碼,帶你徹底看懂。
一、為什么需要 wraps?
裝飾器會(huì)“偷梁換柱”
def dec(f):
def wrapper(*a, **kw):
return f(*a, **kw)
return wrapper
@dec
def add(x, y):
"""add two numbers"""
return x + y
print(add.__name__) # wrapper ← 名字被換掉了!
print(add.__doc__) # None ← 文檔串也沒了結(jié)果調(diào)試、IDE 提示、Sphinx 文檔生成全都認(rèn)錯(cuò)人。
functools.wraps 的作用
把原函數(shù)的 名字、文檔、參數(shù)表、注解、模塊名、屬性字典 等關(guān)鍵元信息一次性拷貝到包裝器上,解決“穿幫”問題。
二、最簡正確姿勢
from functools import wraps
def dec(f):
@wraps(f) # 就加這一行
def wrapper(*a, **kw):
"""wrapper doc"""
print('before')
return f(*a, **kw)
return wrapper
@dec
def add(x, y):
"""add two numbers"""
return x + y
print(add.__name__) # add
print(add.__doc__) # add two numbers
print(add.__annotations__) # 原函數(shù)注解也保留三、wraps 其實(shí)是個(gè)“偏函數(shù)”
源碼等價(jià)于:
def wraps(wrapped,
assigned=WRAPPER_ASSIGNMENTS, # 要拷貝的七大屬性
updated=WRAPPER_UPDATES): # __dict__ 如何合并
return partial(update_wrapper, # 返回一個(gè)“等待 wrapper 作為參數(shù)”的函數(shù)
wrapped=wrapped,
assigned=assigned,
updated=updated)所以@wraps(func) 先制造一個(gè)“待裝飾器”,再把真正的 wrapper 傳進(jìn)去,完成 update_wrapper(wrapper, func)。
四、七大默認(rèn)拷貝屬性(WRAPPER_ASSIGNMENTS)
__module____name____qualname__(3.3+ 限定名,嵌套類/函數(shù)需要)__doc____annotations__(3.10+ 包括 PEP 563 延遲求值)__dict__的部分合并規(guī)則(見 WRAPPER_UPDATES)__kwdefaults__、__defaults__默認(rèn)參數(shù)值
五、自定義要保留/跳過的屬性
MY_ATTRS = ('__name__', '__doc__', '__my_flag__')
def my_wraps(f):
return wraps(f, assigned=MY_ATTRS)
# 或者只想忽略某個(gè)屬性
from functools import WRAPPER_ASSIGNMENTS
skip_annotation = tuple(a for a in WRAPPER_ASSIGNMENTS if a != '__annotations__')
def dec(f):
@wraps(f, assigned=skip_annotation)
def wrapper(*a, **kw):
...六、保留 wrapper 自己的屬性(疊加)
有時(shí)既想保留原函數(shù),又想給 wrapper 加新屬性,用 updated 參數(shù):
def dec(f):
@wraps(f, updated=()) # 不合并 __dict__,避免覆蓋
def wrapper(*a, **kw):
...
wrapper.decorator = 'my_dec' # 新增標(biāo)記
return wrapper七、帶參數(shù)的裝飾器 + wraps
from functools import wraps
def repeat(n): # ← 這一層接收參數(shù)
def dec(f):
@wraps(f)
def wrapper(*a, **kw):
for _ in range(n):
result = f(*a, **kw)
return result
return wrapper
return dec
@repeat(3)
def hello():
"""say hello"""
print('hi')
hello()
print(hello.__name__) # hello
print(hello.__doc__) # say hello關(guān)鍵點(diǎn):@wraps(f) 一定要放在最內(nèi)層 wrapper 上,而不是 dec 本身。
八、類裝飾器 & 方法也能用
def count_calls(cls):
orig_init = cls.__init__
@wraps(orig_init) # 保留原 __init__ 簽名
def new_init(self, *a, **kw):
self._cnt = 0
orig_init(self, *a, **kw)
cls.__init__ = new_init
return cls九、調(diào)試技巧:一眼看穿“真身”
>>> hello.__wrapped__ # wraps 自動(dòng)加的秘密屬性 <function hello at 0x...> # 指向原始函數(shù) >>> inspect.signature(hello) # 拿到的是原簽名,不會(huì)看到 wrapper 的 *a, **kw
十、常見坑速查
| 現(xiàn)象 | 原因 | 解決 |
|---|---|---|
| TypeError: wraps() missing 1 required positional argument | 把 @wraps 寫成了 @wraps() | 正確寫 @wraps(func) |
| 簽名還是 wrapper 的 (*args, **kwargs) | 用了 wraps 但 IDE 仍顯示通用簽名 | IDE 緩存 / 需 inspect.signature 重新提取 |
| 保留不了自定義屬性 | 默認(rèn) assigned 不包含 | 傳自定義列表 |
十一、一句話總結(jié)
寫裝飾器就兩步:
- 先 from functools import wraps
- 在 wrapper 函數(shù)正上方寫 @wraps(被裝飾函數(shù))
到此這篇關(guān)于python @wraps()裝飾器的使用的文章就介紹到這了,更多相關(guān)python @wraps()內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python中for語句簡單遍歷數(shù)據(jù)的方法
這篇文章主要介紹了python中for語句簡單遍歷數(shù)據(jù)的方法,以一個(gè)簡單實(shí)例形式分析了Python中for語句遍歷數(shù)據(jù)的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-05-05
Python銀行系統(tǒng)實(shí)戰(zhàn)源碼
這篇文章主要為大家詳細(xì)介紹了Python銀行系統(tǒng)實(shí)戰(zhàn)源碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10
Python+wxPython打造智能網(wǎng)頁截圖工具
在網(wǎng)站測試、UI審查或文檔編寫過程中,我們常常需要對(duì)網(wǎng)站的所有頁面進(jìn)行截圖記錄,下面我們就來看看如何使用Python開發(fā)一個(gè)自動(dòng)化工具,實(shí)現(xiàn)一鍵遍歷網(wǎng)站所有鏈接并生成帶截圖的Excel報(bào)告吧2025-10-10
python實(shí)現(xiàn)簡易名片管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)簡易名片管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04
Python turtle繪圖教程之七段數(shù)碼管顯示數(shù)字和字母
這篇文章主要給大家介紹了關(guān)于Python turtle繪圖教程之七段數(shù)碼管顯示數(shù)字和字母的相關(guān)資料,Python是一種流行的編程語言,可用于編寫各種類型的程序,在數(shù)碼管顯示器上數(shù)字8由7條不同的線條組成,需要的朋友可以參考下2023-10-10

