Python 新手突破瓶颈指南:functools.wraps 元数据复制

2024-08-09 14:19:58 浏览数 (2)

在 Python 中,装饰器是非常强大的工具,用于修改或扩展函数的行为。然而,使用装饰器时,我们经常会遇到一个问题:被装饰函数的元数据信息(如名称、文档字符串和参数列表)可能会丢失。这时,functools.wraps 就派上了用场。本文将深入探讨 functools.wraps 的作用,并提供一些实际的应用例子。

functools.wraps

functools.wraps 是 Python 标准库中的一个装饰器,用于将原始函数的元数据复制到装饰器函数中。这些元数据包括函数名称(__name__)、文档字符串(__doc__)和参数签名(__annotations__)等。

基本用法

让我们先来看一个简单的例子,展示 functools.wraps 的基本用法:

代码语言:javascript复制
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.


详细解释

  1. 定义装饰器函数 my_decorator_without_wrapsmy_decorator_with_wraps
    • my_decorator_without_wraps 接受一个函数 func 作为参数,并返回一个新的函数 wrapper,但没有使用 functools.wraps
    • my_decorator_with_wraps 接受一个函数 func 作为参数,并返回一个新的函数 wrapper,使用 functools.wraps(func) 来确保 wrapper 函数具有 func 函数的元数据。
  2. 使用装饰器:
    • @my_decorator_without_wraps 应用于 example_function_without_wraps,即 example_function_without_wrapsmy_decorator_without_wraps 装饰器包装。
    • @my_decorator_with_wraps 应用于 example_function_with_wraps,即 example_function_with_wrapsmy_decorator_with_wraps 装饰器包装。
  3. 查看被装饰函数的信息:
    • 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 都是保持函数原始信息的重要工具。

0 人点赞