前言

装饰器Python中一种强大的语法特性,它允许在不修改原函数代码的情况下,为函数添加新的功能。装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。

应用场景

  1. 日志记录: 装饰器可用于记录函数的调用信息、参数和返回值。
  2. 性能分析: 可以使用装饰器来测量函数的执行时间。
  3. 权限控制: 装饰器可用于限制对某些函数的访问权限。
  4. 缓存: 装饰器可用于实现函数结果的缓存,以提高性能。

内置装饰器

@staticmethod: 将方法定义为静态方法,不需要实例化类即可调用。
@classmethod: 将方法定义为类方法,第一个参数是类本身(通常命名为 cls)。
@property: 将方法转换为属性,使其可以像属性一样访问。

1. 基本语法

    def decorator(func):
        def wrapper(*args, **kwargs):
            # 在调用原函数前的操作
            result = func(*args, **kwargs)
            # 在调用原函数后的操作
            return result
        return wrapper

    @decorator
    def my_function():
        pass

2. 装饰器案例分析

2.1 计时装饰器

    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"函数 {func.__name__} 执行耗时: {end_time - start_time:.4f}秒")
            return result
        return wrapper

    @timer
    def fibonacci(n):
        """计算斐波那契数列"""
        if n <= 1:
            return n
        return fibonacci(n-1) + fibonacci(n-2)

    # 使用
    print(fibonacci(10)) # 55

2.2 日志记录装饰器

    import functools
    from datetime import datetime

    def logger(func):
        """记录函数调用信息的装饰器"""
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            call_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            print(f"[{call_time}] 调用函数: {func.__name__}")
            print(f"    参数: args={args}, kwargs={kwargs}")
            
            try:
                result = func(*args, **kwargs)
                print(f"    返回值: {result}")
                return result
            except Exception as e:
                print(f"    异常: {e}")
                raise
        
        return wrapper

    @logger
    def divide(a, b):
        return a / b

    # 使用
    divide(10, 2)
    divide(10, 0)  # 会记录异常

2.3 参数化装饰器

    import functools

    def repeat(num_times):
        """重复执行函数的装饰器"""
        def decorator_repeat(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                for _ in range(num_times):
                    result = func(*args, **kwargs)
                return result
            return wrapper
        return decorator_repeat

    @repeat(num_times=3)
    def greet(name):
        print(f"Hello, {name}!")

    # 使用
    greet("Andy") 
    # 打印 3次 
    # Hello Andy
    # Hello Andy
    # Hello Andy

2.4 类装饰器

类装饰器可以用于:

添加/修改类的方法或属性
拦截实例化过程
实现单例模式、日志记录、权限检查等功能

类装饰器有两种常见形式:

函数形式的类装饰器(接收类作为参数,返回新类)
类形式的类装饰器(实现 __call__ 方法,使其可调用)

    class CountCalls:
        """统计函数调用次数的类装饰器"""
        def __init__(self, func):
            functools.update_wrapper(self, func)
            self.func = func
            self.num_calls = 0
        
        def __call__(self, *args, **kwargs):
            self.num_calls += 1
            print(f"调用 {self.func.__name__}{self.num_calls} 次")
            return self.func(*args, **kwargs)

    @CountCalls
    def say_hello():
        print("Hello!")

    # 使用
    say_hello()
    say_hello()
    print(f"总调用次数: {say_hello.num_calls}")

2.5 权限验证装饰器

    import functools

    def require_role(required_role):
        """基于角色的权限验证装饰器"""
        def decorator(func):
            @functools.wraps(func)
            def wrapper(user, *args, **kwargs):
                if user.get('role') != required_role:
                    raise PermissionError(f"需要 {required_role} 权限,当前用户权限: {user.get('role')}")
                return func(user, *args, **kwargs)
            return wrapper
        return decorator

    # 用户数据
    users = [
        {'name': 'Alice', 'role': 'admin'},
        {'name': 'Bob', 'role': 'user'}
    ]

    @require_role('admin')
    def delete_user(current_user, username):
        print(f"{current_user['name']} 删除了用户 {username}")

    # 使用
    try:
        delete_user(users[0], "Charlie")  # 成功
        delete_user(users[1], "Charlie")  # 失败
    except PermissionError as e:
        print(f"权限错误: {e}")

2.6 缓存装饰器

    import functools
    from typing import Any

    def cache(func):
        """简单的缓存装饰器"""
        cached_results = {}
        
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 创建缓存键
            key = str(args) + str(sorted(kwargs.items()))
            
            if key in cached_results:
                print(f"从缓存中获取 {func.__name__}{args} 的结果")
                return cached_results[key]
            
            result = func(*args, **kwargs)
            cached_results[key] = result
            print(f"计算 {func.__name__}{args} 的结果并缓存")
            return result
        
        return wrapper

    @cache
    def expensive_operation(x, y):
        time.sleep(1)  # 模拟耗时操作
        return x * y

    # 使用
    print(expensive_operation(3, 4))  # 第一次计算
    print(expensive_operation(3, 4))  # 从缓存获取

3. 多个装饰器的使用

    def decorator1(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print("装饰器1 - 前")
            result = func(*args, **kwargs)
            print("装饰器1 - 后")
            return result
        return wrapper

    def decorator2(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print("装饰器2 - 前")
            result = func(*args, **kwargs)
            print("装饰器2 - 后")
            return result
        return wrapper

    @decorator1
    @decorator2
    def example():
        print("原始函数")

    # 使用
    example()
    # 输出:
    # 装饰器1 - 前
    # 装饰器2 - 前
    # 原始函数
    # 装饰器2 - 后
    # 装饰器1 - 后

4. 使用注意事项

4.1 保留元信息

错误做法:

def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def my_function():
    """这是一个测试函数"""
    pass

print(my_function.__name__)  # 输出: wrapper
print(my_function.__doc__)   # 输出: None

正确做法:

    import functools

    def good_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper

    @good_decorator
    def my_function():
        """这是一个测试函数"""
        pass

    print(my_function.__name__)  # 输出: my_function
    print(my_function.__doc__)   # 输出: 这是一个测试函数

4.2 处理带参数的装饰器

import functools

def smart_decorator(condition=True):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if condition:
                print("装饰器功能启用")
            else:
                print("装饰器功能禁用")
            return func(*args, **kwargs)
        return wrapper
    return decorator

# 使用
@smart_decorator(condition=True)
def function1():
    print("函数1执行")

@smart_decorator(condition=False)
def function2():
    print("函数2执行")

4.3 避免装饰器副作用

def safe_decorator(func):
    """安全的装饰器,处理异常"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(f"函数 {func.__name__} 执行出错: {e}")
            # 可以选择返回默认值或重新抛出异常
            return None
    return wrapper

@safe_decorator
def risky_operation():
    return 1 / 0  # 这会引发异常

result = risky_operation()  # 不会崩溃,会打印错误信息

5. 实际应用场景

5.1 Web框架中的路由装饰器

    class Router:
        def __init__(self):
            self.routes = {}
        
        def route(self, path):
            def decorator(func):
                self.routes[path] = func
                return func
            return decorator

    router = Router()

    @router.route("/home")
    def home():
        return "Home Page"

    @router.route("/about")
    def about():
        return "About Page"

    # 使用
    print(router.routes)  # 输出: {'/home': <function home>, '/about': <function about>}

5.2 数据库事务装饰器

def transaction(db_connection):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            try:
                result = func(*args, **kwargs)
                db_connection.commit()
                print("事务提交成功")
                return result
            except Exception as e:
                db_connection.rollback()
                print(f"事务回滚: {e}")
                raise
        return wrapper
    return decorator

总结

装饰器是用途

增强函数功能:添加日志、计时、缓存等功能
代码复用:将通用功能抽象为装饰器
保持代码整洁:避免修改原函数代码
实现AOP:面向切面编程

最佳实践

  1. 始终使用 @functools.wraps 保留函数元信息
  2. 为装饰器编写清晰的文档字符串
  3. 考虑装饰器的性能和副作用
  4. 在团队中建立装饰器使用规范

通过合理使用装饰器,可以大幅提高代码的可维护性和可读性。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐