3.3 Python 函数装饰器详解

引言:增强函数而不修改其代码

在Python编程中,我们经常需要在不修改函数源代码的情况下,为函数添加额外的功能,如日志记录、性能测试、权限校验等。函数装饰器(Decorator) 正是为了解决这类问题而设计的强大工具。

装饰器是Python的一种语法糖,它允许我们通过@符号来修饰函数或方法,从而优雅地扩展其功能。理解装饰器不仅有助于编写更简洁、更可复用的代码,也是深入理解Python高级特性的重要一步。

在本节中,我们将全面探讨Python函数装饰器的原理、实现和应用,并通过丰富的示例展示其强大功能。


第一部分:装饰器基础

1.1 什么是装饰器?

装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。装饰器的作用是在不修改原函数代码的情况下,为函数添加额外的功能。

1.2 装饰器的基本语法

def decorator_func(original_func):
    def wrapper_func(*args, **kwargs):
        # 在调用原函数前执行的代码
        result = original_func(*args, **kwargs)
        # 在调用原函数后执行的代码
        return result
    return wrapper_func

# 使用装饰器
@decorator_func
def my_function():
    pass

上面的@decorator_func语法等价于:

my_function = decorator_func(my_function)

1.3 简单装饰器示例

def simple_decorator(func):
    """一个简单的装饰器,记录函数调用"""
    def wrapper():
        print(f"开始执行 {func.__name__}")
        result = func()
        print(f"完成执行 {func.__name__}")
        return result
    return wrapper

@simple_decorator
def say_hello():
    print("Hello, World!")

# 调用被装饰的函数
say_hello()
# 输出:
# 开始执行 say_hello
# Hello, World!
# 完成执行 say_hello

第二部分:装饰器的工作原理

2.1 装饰器的执行时机

装饰器在函数定义时立即执行,而不是在函数调用时执行:

def decorator(func):
    print(f"装饰器正在装饰: {func.__name__}")
    def wrapper():
        print(" wrapper 被调用")
        return func()
    return wrapper

@decorator
def my_function():
    print(" my_function 被调用")

print("函数定义完成")

# 调用函数
my_function()

# 输出:
# 装饰器正在装饰: my_function
# 函数定义完成
# wrapper 被调用
# my_function 被调用

2.2 保留函数元信息

使用装饰器后,原函数的元信息(如函数名、文档字符串等)会被包装函数的元信息所覆盖。可以使用functools.wraps来保留原函数的元信息:

import functools

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

@preserve_metadata_decorator
def example_function():
    """这是一个示例函数的文档字符串"""
    print("函数执行中")

print(example_function.__name__)      # 输出: example_function
print(example_function.__doc__)       # 输出: 这是一个示例函数的文档字符串

2.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 my_function():
    print("函数执行")

my_function()
# 输出:
# 装饰器1 - 前
# 装饰器2 - 前
# 函数执行
# 装饰器2 - 后
# 装饰器1 - 后

上面的代码等价于:

my_function = decorator1(decorator2(my_function))

第三部分:带参数的装饰器

3.1 装饰器工厂函数

如果需要向装饰器传递参数,需要创建一个返回装饰器的函数(装饰器工厂):

def repeat(num_times):
    """装饰器工厂,返回一个装饰器"""
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper_repeat
    return decorator_repeat

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

greet("Alice")
# 输出:
# Hello Alice
# Hello Alice
# Hello Alice

3.2 带参数的装饰器示例

def log_with_level(level="INFO"):
    """带日志级别的装饰器"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[{level}] 调用函数: {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log_with_level(level="DEBUG")
def debug_function():
    print("调试函数执行")

@log_with_level(level="ERROR")
def error_function():
    print("错误函数执行")

debug_function()  # 输出: [DEBUG] 调用函数: debug_function
error_function()  # 输出: [ERROR] 调用函数: error_function

3.3 可选参数的装饰器

可以创建既支持带参数使用,也支持不带参数使用的装饰器:

def flexible_decorator(func=None, *, prefix="DEFAULT"):
    """灵活的装饰器,支持带参数和不带参数使用"""
    if func is None:
        # 带参数调用
        return lambda f: flexible_decorator(f, prefix=prefix)
    
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[{prefix}] 调用函数: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

# 不带参数使用
@flexible_decorator
def function1():
    print("函数1执行")

# 带参数使用
@flexible_decorator(prefix="CUSTOM")
def function2():
    print("函数2执行")

function1()  # 输出: [DEFAULT] 调用函数: function1
function2()  # 输出: [CUSTOM] 调用函数: function2

第四部分:类装饰器

4.1 作为类的装饰器

除了函数,类也可以作为装饰器使用。类装饰器需要实现__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.num_calls} of {self.func.__name__}")
        return self.func(*args, **kwargs)

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

say_hello()  # 输出: 调用 #1 of say_hello
say_hello()  # 输出: 调用 #2 of say_hello
print(f"总调用次数: {say_hello.num_calls}")  # 输出: 总调用次数: 2

4.2 带参数的类装饰器

类装饰器也可以接受参数:

class Repeat:
    """带参数的类装饰器"""
    
    def __init__(self, num_times):
        self.num_times = num_times
    
    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(self.num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper

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

greet("Bob")  # 输出三次: Hello Bob

第五部分:内置装饰器

Python提供了一些有用的内置装饰器:

5.1 @staticmethod

将方法转换为静态方法,不需要接收self参数:

class MathUtils:
    @staticmethod
    def add(x, y):
        return x + y

# 无需创建实例即可调用
result = MathUtils.add(5, 3)  # 输出: 8

5.2 @classmethod

将方法转换为类方法,第一个参数是类本身(cls)而不是实例(self):

class Person:
    count = 0
    
    def __init__(self, name):
        self.name = name
        Person.count += 1
    
    @classmethod
    def get_count(cls):
        return cls.count

person1 = Person("Alice")
person2 = Person("Bob")
print(Person.get_count())  # 输出: 2

5.3 @property

将方法转换为属性,可以定义getter、setter和deleter:

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        """半径的getter"""
        return self._radius
    
    @radius.setter
    def radius(self, value):
        """半径的setter"""
        if value < 0:
            raise ValueError("半径不能为负")
        self._radius = value
    
    @property
    def area(self):
        """只读属性:面积"""
        return 3.14159 * self._radius ** 2

circle = Circle(5)
print(circle.radius)  # 输出: 5
print(circle.area)    # 输出: 78.53975

circle.radius = 10
print(circle.area)    # 输出: 314.159

# circle.area = 100  # 错误: area是只读属性

第六部分:装饰器的实际应用

6.1 日志记录装饰器

import time
import functools
from datetime import datetime

def log_execution(func):
    """记录函数执行日志的装饰器"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        print(f"[{datetime.now()}] 开始执行 {func.__name__}")
        
        result = func(*args, **kwargs)
        
        end_time = time.time()
        duration = end_time - start_time
        print(f"[{datetime.now()}] 完成执行 {func.__name__}, 耗时: {duration:.4f}秒")
        
        return result
    return wrapper

@log_execution
def complex_calculation():
    """模拟复杂计算"""
    time.sleep(1)
    return 42

result = complex_calculation()

6.2 性能分析装饰器

def profile_performance(func):
    """性能分析装饰器"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        import cProfile
        import pstats
        import io
        
        # 创建性能分析器
        profiler = cProfile.Profile()
        profiler.enable()
        
        result = func(*args, **kwargs)
        
        profiler.disable()
        
        # 输出性能分析结果
        s = io.StringIO()
        ps = pstats.Stats(profiler, stream=s).sort_stats('cumulative')
        ps.print_stats()
        print(s.getvalue())
        
        return result
    return wrapper

@profile_performance
def performance_test():
    """性能测试函数"""
    total = 0
    for i in range(100000):
        total += i
    return total

performance_test()

6.3 缓存装饰器

def cache_results(func):
    """缓存函数结果的装饰器"""
    cache = {}
    
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 创建缓存键
        key = str(args) + str(kwargs)
        
        # 检查缓存
        if key in cache:
            print(f"使用缓存结果: {key}")
            return cache[key]
        
        # 计算并缓存结果
        result = func(*args, **kwargs)
        cache[key] = result
        print(f"计算结果并缓存: {key}")
        
        return result
    
    # 提供清理缓存的方法
    wrapper.clear_cache = lambda: cache.clear()
    
    return wrapper

@cache_results
def expensive_computation(x, y):
    """模拟昂贵计算"""
    time.sleep(1)
    return x * y

# 第一次调用会计算
result1 = expensive_computation(5, 6)  # 输出: 计算结果并缓存: (5, 6){}

# 第二次相同参数调用会使用缓存
result2 = expensive_computation(5, 6)  # 输出: 使用缓存结果: (5, 6){}

# 清理缓存
expensive_computation.clear_cache()

6.4 权限检查装饰器

def require_role(allowed_roles):
    """基于角色的权限检查装饰器"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.get('role') not in allowed_roles:
                raise PermissionError(f"用户 {user.get('name')} 没有执行 {func.__name__} 的权限")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

# 用户数据
admin_user = {'name': 'Alice', 'role': 'admin'}
regular_user = {'name': 'Bob', 'role': 'user'}

@require_role(['admin'])
def delete_user(executor, user_to_delete):
    print(f"{executor['name']} 删除了用户 {user_to_delete}")
    return True

# 管理员可以执行
delete_user(admin_user, "Charlie")  # 成功

# 普通用户不能执行
try:
    delete_user(regular_user, "Dave")  # 抛出 PermissionError
except PermissionError as e:
    print(e)

6.5 重试机制装饰器

def retry_on_failure(max_attempts=3, delay=1, exceptions=(Exception,)):
    """失败重试装饰器"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    attempts += 1
                    if attempts == max_attempts:
                        print(f"函数 {func.__name__}{max_attempts} 次尝试后仍失败")
                        raise
                    
                    print(f"函数 {func.__name__}{attempts} 次尝试失败: {e}, {max_attempts - attempts} 次重试剩余")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry_on_failure(max_attempts=3, delay=0.5, exceptions=(ValueError,))
def unreliable_function():
    """模拟不可靠的函数,可能随机失败"""
    import random
    if random.random() < 0.7:  # 70%的概率失败
        raise ValueError("随机失败")
    return "成功"

# 测试重试机制
try:
    result = unreliable_function()
    print(f"最终结果: {result}")
except ValueError as e:
    print(f"所有尝试都失败了: {e}")

第七部分:装饰器的高级用法

7.1 装饰器注册表

可以创建装饰器来自动注册函数:

# 函数注册表
FUNCTION_REGISTRY = {}

def register_function(name=None):
    """函数注册装饰器"""
    def decorator(func):
        registry_name = name or func.__name__
        FUNCTION_REGISTRY[registry_name] = func
        return func
    return decorator

@register_function("add")
def add_numbers(a, b):
    return a + b

@register_function("multiply")
def multiply_numbers(a, b):
    return a * b

# 使用注册表
print(FUNCTION_REGISTRY)  # 输出: {'add': <function add_numbers>, 'multiply': <function multiply_numbers>}
result = FUNCTION_REGISTRY['add'](5, 3)  # 输出: 8

7.2 基于装饰器的插件系统

# 插件系统
PLUGINS = {}

def register_plugin(func):
    """插件注册装饰器"""
    PLUGINS[func.__name__] = func
    return func

@register_plugin
def plugin_one():
    return "插件一执行"

@register_plugin
def plugin_two():
    return "插件二执行"

# 执行所有插件
def run_all_plugins():
    results = []
    for name, plugin in PLUGINS.items():
        results.append(f"{name}: {plugin()}")
    return results

print(run_all_plugins())  # 输出: ['plugin_one: 插件一执行', 'plugin_two: 插件二执行']

7.3 装饰器与异步函数

装饰器也可以用于异步函数:

import asyncio
import functools

def async_timer(func):
    """异步函数计时装饰器"""
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        start_time = time.time()
        result = await func(*args, **kwargs)
        end_time = time.time()
        print(f"异步函数 {func.__name__} 执行时间: {end_time - start_time:.4f}秒")
        return result
    return wrapper

@async_timer
async def async_task():
    """模拟异步任务"""
    await asyncio.sleep(1)
    return "任务完成"

# 运行异步函数
async def main():
    result = await async_task()
    print(result)

asyncio.run(main())

第八部分:装饰器的最佳实践

8.1 使用functools.wraps

始终使用@functools.wraps来保留原函数的元信息:

import functools

def good_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 装饰器逻辑
        return func(*args, **kwargs)
    return wrapper

8.2 避免装饰器副作用

装饰器不应该有副作用,特别是避免在装饰时执行逻辑(除了返回包装函数):

# 不好的做法:装饰时有副作用
def bad_decorator(func):
    print(f"装饰 {func.__name__}")  # 副作用:在导入时就会执行
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

# 好的做法:装饰时无副作用
def good_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

8.3 保持装饰器简洁

装饰器应该专注于单一职责,避免过于复杂:

# 不好的做法:一个装饰器做多件事
def complex_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 日志记录
        print(f"调用 {func.__name__}")
        # 性能监控
        start_time = time.time()
        # 权限检查
        if not user_has_permission():
            raise PermissionError("无权限")
        # 执行函数
        result = func(*args, **kwargs)
        # 更多逻辑...
        end_time = time.time()
        print(f"耗时: {end_time - start_time}秒")
        return result
    return wrapper

# 好的做法:多个单一职责的装饰器
@log_call
@check_permission
@measure_time
def my_function():
    pass

8.4 考虑使用类装饰器

对于需要维护状态的装饰器,考虑使用类装饰器:

class StatefulDecorator:
    """有状态的装饰器"""
    def __init__(self, func):
        self.func = func
        self.call_count = 0
        functools.update_wrapper(self, func)
    
    def __call__(self, *args, **kwargs):
        self.call_count += 1
        print(f"第 {self.call_count} 次调用 {self.func.__name__}")
        return self.func(*args, **kwargs)

第九部分:常见问题与解决方案

9.1 装饰器与静态方法

当装饰静态方法时,需要注意装饰器的顺序:

class MyClass:
    @staticmethod
    @my_decorator  # 正确的顺序
    def my_method():
        pass
    
    # 错误的顺序会导致问题
    # @my_decorator
    # @staticmethod
    # def my_method():
    #     pass

9.2 装饰器与方法绑定

在类方法中使用装饰器时,需要注意self参数的传递:

def method_decorator(func):
    """适用于方法的装饰器"""
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        print(f"调用方法: {func.__name__}")
        return func(self, *args, **kwargs)
    return wrapper

class MyClass:
    @method_decorator
    def my_method(self, x):
        return x * 2

9.3 调试装饰器

当装饰器导致问题时,可以使用以下方法调试:

# 临时移除装饰器进行测试
def my_function():
    pass

# 原始函数
original_function = my_function.__wrapped__ if hasattr(my_function, '__wrapped__') else my_function

第十部分:总结

Python函数装饰器是一种强大而灵活的工具,它允许我们在不修改原函数代码的情况下,为函数添加额外的功能。通过装饰器,我们可以实现:

  1. 代码复用:将横切关注点(如日志、权限检查)从业务逻辑中分离
  2. 功能增强:为函数添加新功能而不修改其源代码
  3. 代码组织:通过装饰器链清晰地组织多个功能

在实际应用中,装饰器被广泛用于:

  • 日志记录和性能监控
  • 权限检查和身份验证
  • 缓存和记忆化
  • 重试机制和错误处理
  • 插件系统和函数注册

掌握装饰器不仅有助于编写更简洁、更可维护的代码,也是深入理解Python高级特性的重要一步。通过本节的学习,你应该能够创建和使用各种类型的装饰器,并在实际项目中应用它们。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

达文汐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值