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

Python 装饰器:从原理到实战应用的完整指南

admin4个月前 (03-22)Python120

装饰器的核心思想

装饰器本质上是一个接受函数作为参数的函数,它返回一个新的函数。这个新函数通常会包装原函数,在执行前后添加额外的逻辑。装饰器的语法糖 @ 让代码更加简洁优雅。

为什么需要装饰器?

想象一个场景:你正在开发一个大型系统,有几十个函数需要记录执行时间。传统的做法是在每个函数内部添加计时逻辑,但这会导致代码重复,违反 DRY(Don't Repeat Yourself)原则。装饰器提供了一个优雅的解决方案:将计时逻辑封装一次,然后应用到任何需要的函数上。

基础装饰器实现

import time
from functools import wraps

def timer(func):
    """计算函数执行时间的装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        print(f"{func.__name__} 执行耗时: {end_time - start_time:.6f} 秒")
        return result
    return wrapper

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

# 执行测试
calculate_fibonacci(30)

functools.wraps 的作用

在上面的代码中,我们使用了 @wraps(func)。这是一个非常重要的细节。装饰器会替换原函数,如果不使用 wraps,新函数的 __name__、__doc__ 等属性会被覆盖。wraps 装饰器会将原函数的元数据复制到 wrapper 函数上,保持函数的"身份"不变。

带参数的装饰器

有时装饰器需要接受参数,比如重试装饰器需要指定重试次数和延迟时间。这种情况下,我们需要创建一个装饰器工厂函数。

import time
from functools import wraps

def retry(max_attempts=3, delay=1):
    """带参数的重试装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    print(f"第 {attempt + 1} 次尝试失败: {e}")
                    if attempt < max_attempts - 1:
                        time.sleep(delay)
            raise last_exception
        return wrapper
    return decorator

@retry(max_attempts=3, delay=0.5)
def fetch_data(url):
    """模拟可能失败的数据获取"""
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络连接失败")
    return f"数据来自 {url}"

# 执行测试
try:
    data = fetch_data("https://api.example.com/data")
    print(f"成功获取数据: {data}")
except Exception as e:
    print(f"最终失败: {e}")

类装饰器

除了函数装饰器,Python 还支持类装饰器。类装饰器可以用来修改类的行为,比如添加方法、修改属性等。

def add_str_method(cls):
    """为类添加 __str__ 方法的装饰器"""
    def __str__(self):
        attrs = {k: v for k, v in self.__dict__.items() if not k.startswith('_')}
        return f"{cls.__name__}({attrs})"
    cls.__str__ = __str__
    return cls

@add_str_method
class User:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

# 执行测试
user = User("张三", 25, "[email protected]")
print(user)  # 输出: User({'name': '张三', 'age': 25, 'email': '[email protected]'})

装饰器栈:多个装饰器的组合

Python 允许在一个函数上应用多个装饰器,装饰器的执行顺序是从下往上(从内向外)。

import time
from functools import wraps

def log_calls(func):
    """记录函数调用的装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__}")
        result = func(*args, **kwargs)
        print(f"函数 {func.__name__} 执行完成")
        return result
    return wrapper

def cache_results(func):
    """缓存结果的装饰器"""
    cache = {}
    @wraps(func)
    def wrapper(*args, **kwargs):
        key = (args, frozenset(kwargs.items()))
        if key in cache:
            print(f"从缓存中返回结果")
            return cache[key]
        result = func(*args, **kwargs)
        cache[key] = result
        return result
    return wrapper

@log_calls
@cache_results
def expensive_computation(x):
    """耗时计算函数"""
    print("执行复杂计算...")
    time.sleep(0.1)
    return x * x

# 执行测试
print(expensive_computation(5))  # 首次调用,执行计算
print(expensive_computation(5))  # 第二次调用,从缓存返回
print(expensive_computation(10))  # 新参数,执行计算

单例模式的装饰器实现

装饰器在实现设计模式时非常有用,比如单例模式。

def singleton(cls):
    """单例装饰器"""
    instances = {}
    @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("创建数据库连接")
        self.connected = False

    def connect(self):
        if not self.connected:
            print("连接数据库...")
            self.connected = True
        else:
            print("已经连接")

# 执行测试
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(f"db1 和 db2 是同一个对象: {db1 is db2}")  # 输出: True

性能监控装饰器

在实际项目中,性能监控装饰器可以帮助我们快速定位性能瓶颈。

import time
from functools import wraps
from collections import defaultdict

class PerformanceMonitor:
    def __init__(self):
        self.call_times = defaultdict(list)
    
    def monitor(self, func):
        """性能监控装饰器"""
        @wraps(func)
        def wrapper(*args, **kwargs):
            start_time = time.perf_counter()
            result = func(*args, **kwargs)
            end_time = time.perf_counter()
            duration = end_time - start_time
            self.call_times[func.__name__].append(duration)
            return result
        return wrapper
    
    def report(self):
        """生成性能报告"""
        print("\n性能监控报告:")
        print("-" * 40)
        for func_name, times in self.call_times.items():
            avg_time = sum(times) / len(times)
            max_time = max(times)
            min_time = min(times)
            print(f"{func_name}:")
            print(f"  调用次数: {len(times)}")
            print(f"  平均耗时: {avg_time:.6f}s")
            print(f"  最大耗时: {max_time:.6f}s")
            print(f"  最小耗时: {min_time:.6f}s")

# 使用示例
monitor = PerformanceMonitor()

@monitor.monitor
def data_processing(data):
    """数据处理函数"""
    time.sleep(0.01)
    return [x * 2 for x in data]

@monitor.monitor
def save_to_disk(data):
    """保存数据到磁盘"""
    time.sleep(0.005)
    return len(data)

# 执行测试
for _ in range(5):
    data = list(range(100))
    processed = data_processing(data)
    save_to_disk(processed)

monitor.report()

装饰器的最佳实践

1. 始终使用 functools.wraps:保持原函数的元数据,这对于调试和文档生成非常重要。

2. 保持装饰器简单:复杂的装饰器难以维护和测试。如果装饰器逻辑太复杂,考虑将其分解为多个装饰器或使用类。

3. 文档化装饰器:为装饰器编写清晰的文档字符串,说明它的作用和参数。

4. 考虑性能影响:装饰器会增加函数调用的开销,对于性能敏感的代码要谨慎使用。

5. 处理可调用对象:如果装饰器需要应用到方法上,考虑使用描述符协议。

总结

装饰器是 Python 中体现"开放封闭原则"的完美示例——对扩展开放,对修改封闭。通过装饰器,我们可以在不修改原有代码的情况下,为函数和类添加新功能。这种特性使得代码更加模块化、可维护,也更符合 Python 哲学中的优雅和简洁。

掌握装饰器不仅能让你的代码更 Pythonic,还能帮助你更好地理解函数式编程思想、高阶函数和闭包等核心概念。在实际项目中,合理使用装饰器可以大大减少代码重复,提高开发效率和代码质量。

相关文章

[Python 教程] OpenCV 绘图教程:图形与文本标注

OpenCV 绘图教程:图形与文本标注本文介绍如何在 OpenCV 中绘制各种图形和添加文本,用于图像标注和可视化。一、绘制基本图形1.1 创建画布import cv2 import&nb...

Python 装饰器实战:从基础到高级应用的完整指南

装饰器是 Python 中最优雅也最强大的特性之一。它允许你在不修改原函数代码的前提下,动态地添加功能。本文将带你从装饰器的基础概念出发,逐步掌握其在实际开发中的高级应用技巧。许多初学者对装饰器感到困...

Python 装饰器完全指南:从原理到实战的 5 个核心场景

装饰器(Decorator)是 Python 中最具魅力的特性之一。它允许我们在不修改原函数代码的前提下,动态地添加功能。但很多开发者对装饰器的理解仅停留在在函数上面加个@符号的层面。今天,我们将从底...

Python 装饰器实战:从入门到精通

装饰器本质上是一个接受函数作为参数并返回新函数的高阶函数。理解这一点是掌握装饰器的关键。当你看到@decorator 语法时,Python 实际上是在执行 func = decorator(func)...

深入理解 Python 上下文管理器:从基础到高级应用

Python 的 with 语句和上下文管理器是每个开发者都应该掌握的高级技巧,但很多初学者对它的理解仅仅停留在文件操作层面。本文将深入讲解上下文管理器的原理、多种实现方式,以及在实际开发中的高级应用...

Python 生成器进阶:理解 yield 与构建高效迭代器

在 Python 开发中,我们经常需要处理大量数据或流式数据,如果一次性将所有数据加载到内存中,不仅会占用大量内存空间,还可能导致程序运行缓慢甚至崩溃。生成器(Generator)正是解决这个问题的利...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。