当前位置:首页 > Python > 正文

Python wraps函数使用教程 - 保留装饰器函数元信息

Python中wraps函数使用教程

在Python中,装饰器是一个强大的功能,但在使用过程中可能会覆盖被装饰函数的元信息(如函数名、文档字符串等)。functools.wraps函数就是用来解决这个问题的实用工具。

为什么需要wraps函数?

当我们使用装饰器时,实际上是用一个新的函数替换了原始函数。这会导致原始函数的一些元信息丢失:

  • 函数名称(__name__)会变成装饰器内部函数的名称
  • 文档字符串(__doc__)会被覆盖
  • 函数的参数签名信息会丢失
  • 调试信息可能变得不准确

wraps函数的基本用法

functools.wraps是一个装饰器工厂函数,用于更新包装函数的元信息,使其看起来更像原始函数。

基本语法:

from functools import wraps

def decorator(func):
    @wraps(func)  # 使用wraps保留func的元信息
    def wrapper(*args, **kwargs):
        # 装饰器逻辑
        return func(*args, **kwargs)
    return wrapper

使用wraps与不使用wraps的对比

不使用wraps的装饰器

def simple_decorator(func):
    def wrapper(*args, **kwargs):
        """装饰器的文档字符串"""
        print(f"调用函数: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@simple_decorator
def greet(name):
    """问候函数"""
    return f"你好, {name}!"

# 测试元信息
print(greet.__name__)   # 输出: wrapper
print(greet.__doc__)    # 输出: 装饰器的文档字符串

使用wraps的装饰器

from functools import wraps

def better_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """装饰器的文档字符串"""
        print(f"调用函数: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@better_decorator
def greet(name):
    """问候函数"""
    return f"你好, {name}!"

# 测试元信息
print(greet.__name__)   # 输出: greet
print(greet.__doc__)    # 输出: 问候函数

实际应用场景

1. 计时装饰器

import time
from functools import wraps

def timing_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} 执行耗时: {end_time - start_time:.4f}秒")
        return result
    return wrapper

@timing_decorator
def calculate_sum(n):
    """计算从1到n的总和"""
    return sum(range(1, n+1))

print(calculate_sum(1000000))
print(calculate_sum.__name__)  # 输出: calculate_sum
print(calculate_sum.__doc__)   # 输出: 计算从1到n的总和

2. 权限验证装饰器

from functools import wraps

def requires_admin(func):
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.get('is_admin', False):
            raise PermissionError("需要管理员权限")
        return func(user, *args, **kwargs)
    return wrapper

@requires_admin
def delete_user(user, username):
    """删除指定用户"""
    print(f"正在删除用户: {username}")
    return True

# 测试
admin_user = {'name': 'admin', 'is_admin': True}
regular_user = {'name': 'user', 'is_admin': False}

print(delete_user(admin_user, "test_user"))  # 成功执行
try:
    delete_user(regular_user, "test_user")   # 抛出异常
except PermissionError as e:
    print(f"错误: {e}")

print(delete_user.__name__)  # 输出: delete_user

wraps的高级用法

wraps函数可以接受一些可选参数来进一步自定义行为:

def custom_decorator(func):
    @wraps(
        func,
        assigned=('__module__', '__name__', '__qualname__', '__doc__', '__annotations__'),
        updated=('__dict__',)
    )
    def wrapper(*args, **kwargs):
        # 装饰器逻辑
        return func(*args, **kwargs)
    return wrapper
  • assigned: 指定要更新的属性(默认为WRAPPER_ASSIGNMENTS)
  • updated: 指定要合并的属性(默认为WRAPPER_UPDATES)

总结

  • functools.wraps是创建装饰器时的最佳实践
  • 使用wraps可以保留原始函数的元信息(名称、文档字符串等)
  • 使用wraps后,调试和文档生成工具能够正常工作
  • 在定义任何装饰器时,都应该使用@wraps(func)
  • wraps也适用于类装饰器

通过本文,您应该已经掌握了Python中functools.wraps函数的使用方法和重要性。在实际开发中,使用wraps可以避免许多元信息相关的问题,提高代码的可维护性和可读性。

发表评论