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

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

admin2周前 (03-22)Python22

装饰器的核心思想

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

为什么需要装饰器?

想象一个场景:你正在开发一个大型系统,有几十个函数需要记录执行时间。传统的做法是在每个函数内部添加计时逻辑,但这会导致代码重复,违反 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, "zhangsan@example.com")
print(user)  # 输出: User({'name': '张三', 'age': 25, 'email': 'zhangsan@example.com'})

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

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 装饰器实用技巧:从入门到精通

装饰器是 Python 最强大的特性之一,但也是很多开发者感到困惑的概念。简单来说,装饰器是一个函数,它接受另一个函数作为输入,并返回一个新的函数。使用装饰器,你可以在不修改原函数代码的情况下,为其添...

Python 上下文管理器的 5 个实用技巧,让你的代码更优雅

在 Python 编程中,上下文管理器(Context Manager)是一个优雅的资源管理工具。你可能已经熟悉最常见的用法——使用 with 语句打开文件,但上下文管理器的能力远不止于此。今天,我将...

Python 装饰器的高级应用与实战技巧

装饰器本质上是接受函数作为参数并返回新函数的高阶函数。理解这一点是掌握装饰器的关键。让我们从基础开始,逐步深入到高级应用。首先,我们需要理解函数在 Python 中是一等公民。这意味着函数可以像其他对...

Python 上下文管理器:不只是 with 语句那么简单

在 Python 编程中,上下文管理器(Context Manager)是一个被低估的强大工具。大多数开发者只知道用 with open() 来安全地处理文件,但实际上,上下文管理器的应用场景远不止于...

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

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

Python 装饰器进阶:从理解到实战

装饰器是 Python 中一个非常强大的特性,它允许你在不修改原函数代码的情况下,为函数添加额外的功能。很多开发者虽然用过装饰器,但对其底层原理和高级用法理解不深。本文将从基础出发,深入讲解装饰器的工...

发表评论

访客

看不清,换一张

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