引言

Python装饰器(Decorator)是Python语言中一个强大而优雅的特性,它允许开发者在不修改原有函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个高阶函数,它接收一个函数作为参数并返回一个新的函数。这种设计模式遵循了软件工程中的开放-封闭原则,即对扩展开放,对修改封闭。

装饰器的语法使用Python的@符号,这使得代码更加简洁和可读。通过装饰器,我们可以轻松实现日志记录、性能测试、权限验证、事务处理等横切关注点(Cross-Cutting Concerns),从而保持核心业务逻辑的清晰和纯粹。

装饰器的基本概念

什么是装饰器

装饰器是一个可调用对象(callable),它接收一个函数作为参数,并返回一个新的函数。装饰器可以在不修改原函数代码的情况下,为函数添加新的功能。

# 最简单的装饰器示例
def my_decorator(func):
    def wrapper():
        print("在函数调用前执行")
        func()
        print("在函数调用后执行")
    return wrapper

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

# 调用装饰后的函数
say_hello()

输出结果:

在函数调用前执行
Hello!
在函数调用后执行

装饰器的工作原理

当Python解释器遇到@decorator语法时,它实际上执行了以下操作:

# @my_decorator 语法等价于:
def say_hello():
    print("Hello!")
say_hello = my_decorator(say_hello)

这意味着装饰器在函数定义时就被调用,并将原函数替换为装饰器返回的新函数。

带参数的函数装饰

处理带参数的函数

当被装饰的函数需要参数时,我们需要在包装函数中也接受相应的参数:

def greet_decorator(func):
    def wrapper(*args, **kwargs):
        print("准备执行函数...")
        result = func(*args, **kwargs)
        print("函数执行完成!")
        return result
    return wrapper

@greet_decorator
def add(a, b):
    return a + b

print(add(3, 5))

输出:

准备执行函数...
函数执行完成!
8

保留原函数元信息

默认情况下,装饰器会隐藏原函数的名称和文档字符串。使用functools.wraps可以解决这个问题:

import functools

def debug_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__},参数: {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"函数返回: {result}")
        return result
    return wrapper

@debug_decorator
def multiply(x, y):
    """计算两个数的乘积"""
    return x * y

print(multiply.__name__)  # 输出: multiply
print(multiply.__doc__)   # 输出: 计算两个数的乘积

带参数的装饰器

装饰器工厂

有时我们需要让装饰器本身接受参数,这需要创建一个装饰器工厂函数:

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"你好, {name}!")

greet("Alice")

输出:

你好, Alice!
你好, Alice!
你好, Alice!

类装饰器

除了函数装饰器,Python还支持类装饰器。类装饰器需要实现__call__方法:

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0
        functools.update_wrapper(self, func)
    
    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("大家好!")

say_hello()
say_hello()

输出:

函数 say_hello 被调用了 1 次
大家好!
函数 say_hello 被调用了 2 次
大家好!

实际应用案例

日志记录装饰器

import logging
import time

def log_execution_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        logging.info(f"函数 {func.__name__} 执行耗时: {execution_time:.4f}秒")
        return result
    return wrapper

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

@log_execution_time
def slow_function():
    time.sleep(1)
    return "完成"

slow_function()

权限验证装饰器

class User:
    def __init__(self, username, role):
        self.username = username
        self.role = role

def require_role(required_role):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.role != required_role:
                raise PermissionError(f"需要 {required_role} 权限,当前用户权限: {user.role}")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def delete_user(user, username):
    print(f"用户 {user.username} 删除了用户 {username}")

# 测试
admin_user = User("admin", "admin")
regular_user = User("bob", "user")

delete_user(admin_user, "charlie")  # 成功
# delete_user(regular_user, "charlie")  # 抛出 PermissionError

缓存装饰器

import time

def cache(seconds):
    def decorator_cache(func):
        cache_dict = {}
        @functools.wraps(func)
        def wrapper(*args):
            # 检查缓存
            current_time = time.time()
            if args in cache_dict:
                result, timestamp = cache_dict[args]
                if current_time - timestamp < seconds:
                    print("从缓存中获取结果")
                    return result
            
            # 执行函数并缓存结果
            result = func(*args)
            cache_dict[args] = (result, current_time)
            print("计算新结果并缓存")
            return result
        return wrapper
    return decorator_cache

@cache(5)  # 缓存5秒
def expensive_calculation(n):
    time.sleep(1)  # 模拟耗时计算
    return n * n

print(expensive_calculation(4))  # 计算新结果并缓存
time.sleep(2)
print(expensive_calculation(4))  # 从缓存中获取结果
time.sleep(4)
print(expensive_calculation(4))  # 计算新结果并缓存

装饰器的组合使用

多个装饰器的堆叠

Python允许多个装饰器堆叠使用,它们的执行顺序是从下到上:

def decorator_a(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("装饰器A - 开始")
        result = func(*args, **kwargs)
        print("装饰器A - 结束")
        return result
    return wrapper

def decorator_b(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("装饰器B - 开始")
        Python装饰器是Python语言中一个强大而优雅的特性,它允许开发者在不修改原有函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个高阶函数,它接收一个函数作为参数并返回一个新的函数。这种设计模式遵循了软件工程中的开放-封闭原则,即对扩展开放,对修改封闭。

装饰器的语法使用Python的`@`符号,这使得代码更加简洁和可读。通过装饰器,我们可以轻松实现日志记录、性能测试、权限验证、事务处理等横切关注点(Cross-Cutting Concerns),从而保持核心业务逻辑的清晰和纯粹。

## 装饰器的基本概念

### 什么是装饰器

装饰器是一个可调用对象(callable),它接收一个函数作为参数,并返回一个新的函数。装饰器可以在不修改原函数代码的情况下,为函数添加新的功能。

```python
# 最简单的装饰器示例
def my_decorator(func):
    def wrapper():
        print("在函数调用前执行")
        func()
        print("在函数调用后执行")
    return wrapper

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

# 调用装饰后的函数
say_hello()

输出结果:

在函数调用前执行
Hello!
在函数调用后执行

装饰器的工作原理

当Python解释器遇到@decorator语法时,它实际上执行了以下操作:

# @my_decorator 语法等价于:
def say_hello():
    print("Hello!")
say_hello = my_decorator(say_hello)

这意味着装饰器在函数定义时就被调用,并将原函数替换为装饰器返回的新函数。

带参数的函数装饰

处理带参数的函数

当被装饰的函数需要参数时,我们需要在包装函数中也接受相应的参数:

def greet_decorator(func):
    def wrapper(*args, **kwargs):
        print("准备执行函数...")
        result = func(*args, **kwargs)
        print("函数执行完成!")
        return result
    return wrapper

@greet_decorator
def add(a, b):
    return a + b

print(add(3, 5))

输出:

准备执行函数...
函数执行完成!
8

保留原函数元信息

默认情况下,装饰器会隐藏原函数的名称和文档字符串。使用functools.wraps可以解决这个问题:

import functools

def debug_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__},参数: {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"函数返回: {result}")
        return result
    return wrapper

@debug_decorator
def multiply(x, y):
    """计算两个数的乘积"""
    return x * y

print(multiply.__name__)  # 输出: multiply
print(multiply.__doc__)   # 输出: 计算两个数的乘积

带参数的装饰器

装饰器工厂

有时我们需要让装饰器本身接受参数,这需要创建一个装饰器工厂函数:

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"你好, {name}!")

greet("Alice")

输出:

你好, Alice!
你好, Alice!
你好, Alice!

类装饰器

除了函数装饰器,Python还支持类装饰器。类装饰器需要实现__call__方法:

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0
        functools.update_wrapper(self, func)
    
    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("大家好!")

say_hello()
say_hello()

输出:

函数 say_hello 被调用了 1 次
大家好!
函数 say_hello 被调用了 2 次
大家好!

实际应用案例

日志记录装饰器

import logging
import time

def log_execution_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        logging.info(f"函数 {func.__name__} 执行耗时: {execution_time:.4f}秒")
        return result
    return wrapper

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

@log_execution_time
def slow_function():
    time.sleep(1)
    return "完成"

slow_function()

权限验证装饰器

class User:
    def __init__(self, username, role):
        self.username = username
        self.role = role

def require_role(required_role):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.role != required_role:
                raise PermissionError(f"需要 {required_role} 权限,当前用户权限: {user.role}")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def delete_user(user, username):
    print(f"用户 {user.username} 删除了用户 {username}")

# 测试
admin_user = User("admin", "admin")
regular_user = User("bob", "user")

delete_user(admin_user, "charlie")  # 成功
# delete_user(regular_user, "charlie")  # 抛出 PermissionError

缓存装饰器

import time

def cache(seconds):
    def decorator_cache(func):
        cache_dict = {}
        @functools.wraps(func)
        def wrapper(*args):
            # 检查缓存
            current_time = time.time()
            if args in cache_dict:
                result, timestamp = cache_dict[args]
                if current_time - timestamp < seconds:
                    print("从缓存中获取结果")
                    return result
            
            # 执行函数并缓存结果
            result = func(*args)
            cache_dict[args] = (result, current_time)
            print("计算新结果并缓存")
            return result
        return wrapper
    return decorator_cache

@cache(5)  # 缓存5秒
def expensive_calculation(n):
    time.sleep(1)  # 模拟耗时计算
    return n * n

print(expensive_calculation(4))  # 计算新结果并缓存
time.sleep(2)
print(expensive_calculation(4))  # 从缓存中获取结果
time.sleep(4)
print(expensive_calculation(4))  # 计算新结果并缓存

装饰器的组合使用

多个装饰器的堆叠

Python允许多个装饰器堆叠使用,它们的执行顺序是从下到上:

def decorator_a(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("装饰器A - 开始")
        result = func(*args, **kwargs)
        print("装饰器A - 结束")
        return result
    return wrapper

def decorator_b(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("装饰器B - 开始")
        result = func(*args, **kwargs)
        print("装饰器B - 结束")
        return result
    return wrapper

@decorator_a
@decorator_b
def my_function():
    print("执行核心函数")

my_function()

输出:

装饰器A - 开始
装饰器B - 开始
执行核心函数
装饰器B - 结束
装饰器A - 结束

类方法装饰器

装饰器同样适用于类的方法:

class Calculator:
    @staticmethod
    @log_execution_time
    def slow_add(a, b):
        time.sleep(0.5)
        return a + b
    
    @require_role("admin")
    def reset(self, user):
        print(f"用户 {user.username} 重置了计算器")

calc = Calculator()
print(Calculator.slow_add(2, 3))

admin = User("admin", "admin")
calc.reset(admin)

高级装饰器技巧

使用装饰器实现单例模式

def singleton(cls):
    instances = {}
    @functools.wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class DatabaseConnection:
    def __init__(self):
        print("建立数据库连接...")

# 无论创建多少次,都返回同一个实例
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2)  # True

装饰器与上下文管理器结合

def transactional(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("开始事务")
        try:
            result = func(*args, **kwargs)
            print("提交事务")
            return result
        except Exception as e:
            print(f"回滚事务: {e}")
            raise
    return wrapper

@transactional
def transfer_money(from_account, to_account, amount):
    print(f"从 {from_account} 转账 {amount} 到 {to_account}")
    # 模拟可能失败的操作
    if amount < 0:
        raise ValueError("金额不能为负")
    return True

transfer_money("A", "B", 100)
# transfer_money("A", "B", -50)  # 会回滚

装饰器的最佳实践

1. 保持装饰器的简单性

装饰器应该专注于单一职责,避免过于复杂。如果逻辑太复杂,考虑将其拆分为多个装饰器。

2. 使用functools.wraps

始终使用functools.wraps来保留原函数的元信息,这对于调试和文档生成非常重要。

3. 考虑装饰器的性能影响

装饰器会增加函数调用的开销,特别是在嵌套使用时。对于性能敏感的代码,需要评估装饰器带来的影响。

4. 提供清晰的错误信息

在装饰器中添加适当的错误处理和日志,帮助开发者理解问题。

def validate_input(*arg_types):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if len(args) != len(arg_types):
                raise ValueError(f"期望 {len(arg_types)} 个参数,但得到 {len(args)}")
            for i, (arg, expected_type) in enumerate(zip(args, arg_types)):
                if not isinstance(arg, expected_type):
                    raise TypeError(f"参数 {i} 应该是 {expected_type.__name__},但得到 {type(arg).__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate_input(int, int)
def add_numbers(a, b):
    return a + b

# add_numbers("1", 2)  # TypeError

总结

Python装饰器是一个强大而灵活的工具,它允许我们以声明式的方式为函数添加功能。通过本文的详细介绍和丰富的代码示例,我们学习了:

  1. 装饰器的基本概念:理解装饰器作为高阶函数的工作原理
  2. 基础装饰器实现:如何创建和使用简单的装饰器
  3. 带参数的装饰器:通过装饰器工厂实现参数化装饰
  4. 类装饰器:使用类来实现更复杂的装饰逻辑
  5. 实际应用案例:日志记录、权限验证、缓存等实用场景
  6. 装饰器组合:多个装饰器的堆叠使用
  7. 高级技巧:单例模式、事务处理等高级应用
  8. 最佳实践:编写高质量装饰器的指导原则

掌握装饰器不仅能提高代码的可读性和可维护性,还能让我们更好地理解Python的函数式编程特性。在实际开发中,合理使用装饰器可以大大减少代码重复,提高开发效率。

记住,装饰器的核心思想是”在不修改原有代码的基础上扩展功能”,这正是软件工程中”开放-封闭原则”的完美体现。随着对装饰器理解的深入,你会发现它在Python编程中的无限可能。