在 Python 中,装饰器是非常强大的工具,用于修改或扩展函数的行为。然而,使用装饰器时,我们经常会遇到一个问题:被装饰函数的元数据信息(如名称、文档字符串和参数列表)可能会丢失。这时,functools.wraps
就派上了用场。本文将深入探讨 functools.wraps
的作用,并提供一些实际的应用例子。
functools.wraps
functools.wraps 是 Python 标准库中的一个装饰器,用于将原始函数的元数据复制到装饰器函数中。这些元数据包括函数名称(__name__
)、文档字符串(__doc__
)和参数签名(__annotations__
)等。
基本用法
让我们先来看一个简单的例子,展示 functools.wraps
的基本用法:
import functools
# 不使用 functools.wraps 的装饰器
def my_decorator_without_wraps(func):
def wrapper(*args, **kwargs):
"""Wrapper function docstring."""
print(f'Calling function: {func.__name__}')
return func(*args, **kwargs)
return wrapper
# 使用 functools.wraps 的装饰器
def my_decorator_with_wraps(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Wrapper function docstring."""
print(f'Calling function: {func.__name__}')
return func(*args, **kwargs)
return wrapper
# 被装饰的函数
@my_decorator_without_wraps
def example_function_without_wraps(x, y):
"""Example function docstring."""
return x y
@my_decorator_with_wraps
def example_function_with_wraps(x, y):
"""Example function docstring."""
return x y
# 比较输出
print('Without wraps:')
print(example_function_without_wraps.__name__) # 输出: wrapper
print(example_function_without_wraps.__doc__) # 输出: Wrapper function docstring.
print('nWith wraps:')
print(example_function_with_wraps.__name__) # 输出: example_function_with_wraps
print(example_function_with_wraps.__doc__) # 输出: Example function docstring.
详细解释
- 定义装饰器函数
my_decorator_without_wraps
和my_decorator_with_wraps
:my_decorator_without_wraps
接受一个函数 func 作为参数,并返回一个新的函数 wrapper,但没有使用functools.wraps
。my_decorator_with_wraps
接受一个函数 func 作为参数,并返回一个新的函数 wrapper,使用functools.wraps(func)
来确保 wrapper 函数具有 func 函数的元数据。
- 使用装饰器:
@my_decorator_without_wraps
应用于example_function_without_wraps
,即example_function_without_wraps
被my_decorator_without_wraps
装饰器包装。@my_decorator_with_wraps
应用于example_function_with_wraps
,即example_function_with_wraps
被my_decorator_with_wraps
装饰器包装。
- 查看被装饰函数的信息:
example_function_without_wraps.__name__
输出被装饰函数的名称,这里输出 wrapper,而不是example_function_without_wraps
。example_function_without_wraps.__doc__
输出被装饰函数的文档字符串,这里输出Wrapper function docstring
.,而不是Example function docstring
.。example_function_with_wraps.__name__
输出被装饰函数的名称,这里输出example_function_with_wraps
,而不是 wrapper。example_function_with_wraps.__doc__
输出被装饰函数的文档字符串,这里输出Example function docstring
.,而不是 Wrapper function docstring.。
通过这个对比示例,可以清楚地看到 functools.wraps 的作用。它可以保留被装饰函数的元数据,使得装饰器不会意外地修改函数的元信息,从而提高代码的可维护性和可读性。
实际应用场景
1. 计时装饰器
我们可以使用 functools.wraps 来创建一个计时装饰器,用于测量函数执行的时间。
代码语言:javascript复制import time
import functools
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')
return result
return wrapper
@timer
def example_function(n):
"""Example function that sleeps for n seconds."""
time.sleep(n)
example_function(2)
print(example_function.__name__) # 输出: example_function
print(example_function.__doc__) # 输出: Example function that sleeps for n seconds.
2. 权限检查装饰器
我们可以创建一个装饰器来检查用户权限,如果
用户没有足够的权限,则抛出异常。
代码语言:javascript复制import functools
def requires_permission(permission):
def decorator(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if user.permission < permission:
raise PermissionError("Insufficient permissions")
return func(user, *args, **kwargs)
return wrapper
return decorator
@requires_permission(2)
def example_function(user):
"""Example function that requires permission level 2."""
return "Access granted"
class User:
def __init__(self, permission):
self.permission = permission
user = User(permission=1)
try:
example_function(user)
except PermissionError as e:
print(e) # 输出: Insufficient permissions
print(example_function.__name__) # 输出: example_function
print(example_function.__doc__) # 输出: Example function that requires permission level 2.
3. 日志记录装饰器
我们可以使用装饰器在函数调用前后记录日志信息
代码语言:javascript复制import functools
def logger(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f'Calling {func.__name__} with args: {args}, kwargs: {kwargs}')
result = func(*args, **kwargs)
print(f'{func.__name__} returned {result}')
return result
return wrapper
@logger
def example_function(a, b):
"""Example function that adds two numbers."""
return a b
example_function(3, 4)
print(example_function.__name__) # 输出: example_function
print(example_function.__doc__) # 输出: Example function that adds two numbers.
总结
通过使用 functools.wraps,我们可以确保装饰器不会意外地修改被装饰函数的元数据,从而提高代码的可维护性和可读性。无论是简单的计时装饰器、权限检查装饰器,还是日志记录装饰器,functools.wraps 都是保持函数原始信息的重要工具。