要解决装饰器“吞噬”原始函数元信息的问题,必须使用functools.wraps装饰器,它能将原始函数的__name__、__doc__、__module__等属性复制到包装函数上,并保留__wrapped__属性指向原函数,从而确保被装饰函数在调试、文档生成、ide提示、测试发现等场景中仍表现得像原始函数一样,避免元数据丢失带来的各种问题,最终实现装饰器的透明性,完整保留函数的身份和元信息。

在Python中,当你使用装饰器(decorator)来修改或增强一个函数时,原始函数的一些重要元信息,比如它的名字(
__name__
__doc__
__module__
functools
wraps
要解决装饰器“吞噬”原始函数元信息的问题,核心在于在你的自定义装饰器内部,将
functools.wraps
wrapper
inner
这里是一个直观的对比:
立即学习“Python免费学习笔记(深入)”;
1. 没有使用 functools.wraps
def my_simple_decorator(func):
def wrapper(*args, **kwargs):
"""这是一个包装函数的文档字符串。"""
print(f"Calling {func.__name__}...")
result = func(*args, **kwargs)
print(f"{func.__name__} finished.")
return result
return wrapper
@my_simple_decorator
def greet(name):
"""向指定的人打招呼。"""
return f"Hello, {name}!"
print(f"函数名(不使用wraps):{greet.__name__}")
print(f"文档字符串(不使用wraps):{greet.__doc__}")
print(f"模块(不使用wraps):{greet.__module__}")
# 调用函数,功能正常
print(greet("Alice"))
# 预期输出会是 wrapper 的信息,而不是 greet 的
# 函数名(不使用wraps):wrapper
# 文档字符串(不使用wraps):这是一个包装函数的文档字符串。
# 模块(不使用wraps):__main__你会发现
greet
__name__
wrapper
__doc__
wrapper
wrapper
greet
2. 使用 functools.wraps
import functools
def my_decorator_with_wraps(func):
@functools.wraps(func) # 关键在这里!
def wrapper(*args, **kwargs):
"""这是一个包装函数的文档字符串,但会被原始函数的覆盖。"""
print(f"Calling {func.__name__}...")
result = func(*args, **kwargs)
print(f"{func.__name__} finished.")
return result
return wrapper
@my_decorator_with_wraps
def say_hello(name):
"""这是一个原始函数的文档字符串,用于问候。"""
return f"Hello there, {name}!"
print(f"函数名(使用wraps):{say_hello.__name__}")
print(f"文档字符串(使用wraps):{say_hello.__doc__}")
print(f"模块(使用wraps):{say_hello.__module__}")
print(f"原始函数(使用wraps):{say_hello.__wrapped__.__name__}") # functools.wraps 还会添加 __wrapped__ 属性
# 调用函数,功能正常
print(say_hello("Bob"))
# 预期输出会是 say_hello 的信息
# 函数名(使用wraps):say_hello
# 文档字符串(使用wraps):这是一个原始函数的文档字符串,用于问候。
# 模块(使用wraps):__main__
# 原始函数(使用wraps):say_hello通过在
wrapper
@functools.wraps(func)
say_hello
wraps
__wrapped__
这其实是Python函数和作用域机制的一个自然结果,并非什么“bug”。当你定义一个装饰器时,它的本质是一个接受函数A作为参数,然后返回一个新函数B的函数。这个新函数B(也就是我们常说的
wrapper
greet
say_hello
Python中的每个函数对象都有它自己的属性,比如
__name__
__doc__
__module__
functools.wraps
wrapper
greet = my_simple_decorator(greet)
greet
greet
my_simple_decorator
wrapper
greet.__name__
wrapper
greet
从某种角度看,这就像你把一本书(原始函数)放进了一个漂亮的包装盒(
wrapper
functools.wraps
functools.wraps
functools.wraps
func
functools.update_wrapper
update_wrapper
func
__module__
__name__
__qualname__
__doc__
__annotations__
wrapper
say_hello
__name__
__doc__
say_hello
wrapper
__wrapped__
update_wrapper
wrapper
__wrapped__
func
func.__wrapped__
所以,
functools.wraps
functools.wraps
函数元信息的重要性远不止于解决装饰器的问题,它在Python生态系统的多个层面都扮演着关键角色。
调试和错误追踪: 当程序出现异常时,堆栈跟踪会显示函数名。如果函数名被
wrapper
wraps
wraps
自动化文档生成: 像Sphinx这样的文档生成工具,会大量依赖函数的
__doc__
__doc__
wrapper
__name__
__module__
IDE和静态分析工具: 现代集成开发环境(IDE),比如PyCharm或VS Code,以及像MyPy这样的静态类型检查工具,都会利用函数的元信息来提供代码补全、参数提示、类型检查和重构等功能。如果
__name__
__annotations__
Web框架和路由: 许多Python Web框架(如Flask、Django、FastAPI)广泛使用装饰器来定义路由、视图函数或权限控制。它们常常需要内省这些被装饰的函数,例如,Flask可能会根据函数名生成URL,或者根据函数签名来自动处理请求参数。如果元信息丢失,这些框架的魔力就会消失。
测试框架: Pytest等测试框架在发现测试函数时,通常会查找以
test_
__name__
序列化和反序列化: 在某些高级场景中,你可能需要序列化函数引用,并在之后反序列化它们。函数的
__module__
__qualname__
函数签名检查和适配: 比如,一些RPC框架或者插件系统,可能需要动态地检查函数的签名(参数列表、返回值类型)来确保兼容性。
inspect
functools.wraps
inspect.signature()
总的来说,函数元信息就像是函数的“身份证”和“说明书”。在Python这种高度动态和反射能力的语言中,正确地维护这些信息,是构建健壮、可维护和易于理解的应用程序的关键。
以上就是Python函数如何用 functools.wraps 保留函数元信息 Python函数装饰器元信息保护的方法的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号