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

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

admin2周前 (03-23)Python20

装饰器是 Python 中最强大的特性之一,它允许我们在不修改原函数代码的情况下,为函数添加额外的功能。本文将深入探讨装饰器的高级用法,包括带参数的装饰器、类装饰器、装饰器链以及装饰器在缓存、日志、权限控制等场景中的实际应用。

一、装饰器器基础回顾

装饰器本质上是一个可调用的对象,它接受一个函数作为参数,并返回一个新的函数。最简单的装饰器如下:

def simple_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@simple_decorator
def greet(name):
    return f"你好,{name}!"

# 使用装饰器后的函数
print(greet("小豆包"))

输出结果:

调用函数: greet
你好,小豆包!

二、带参数的装饰器

有时候我们需要为装饰器传递参数。这需要创建一个装饰器工厂函数,它返回一个真正的装饰器。

示例:执行次数限制器

def max_calls(max_count):
    def decorator(func):
        call_count = 0
        def wrapper(*args, **kwargs):
            nonlocal call_count
            if call_count >= max_count:
                raise Exception(f"函数 {func.__name__} 调用次数已达上限 {max_count}")
            call_count += 1
            print(f"第 {call_count} 次调用 {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@max_calls(3)
def expensive_operation():
    return "执行耗时操作"

# 测试调用限制
print(expensive_operation())  # 第 1 次
print(expensive_operation())  # 第 2 次
print(expensive_operation())  # 第 3 次
# print(expensive_operation())  # 抛出异常

这个装饰器非常适合保护那些不应该被频繁调用的资源密集型函数,比如数据库查询、API 请求等。

三、类装饰器

装饰器不仅可以是函数,也可以是类。类装饰器通过实现 __call__ 方法使其实例可调用。

示例:函数计时器

import time
from functools import wraps

class Timer:
    def __init__(self, verbose=True):
        self.verbose = verbose
    
    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start_time = time.perf_counter()
            result = func(*args, **kwargs)
            end_time = time.perf_counter()
            elapsed = end_time - start_time
            
            if self.verbose:
                print(f"{func.__name__} 执行时间: {elapsed:.6f} 秒")
            
            return result
        return wrapper

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

# 使用缓存优化后的版本
@Timer(verbose=True)
def fibonacci_optimized(n, memo={}):
    """带缓存的斐波那契数列"""
    if n in memo:
        return memo[n]
    if n <= 1:
        return n
    memo[n] = fibonacci_optimized(n-1, memo) + fibonacci_optimized(n-2, memo)
    return memo[n]

print(f"fibonacci(30) = {fibonacci_optimized(30)}")

四、装饰器链

多个装饰器可以叠加使用,执行顺序是从下到上(从内到外)。

示例:日志 + 认证 + 缓存

from functools import lru_cache

def log_execution(func):
    """记录函数执行的装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[LOG] 调用 {func.__name__}, 参数: args={args}, kwargs={kwargs}")
        try:
            result = func(*args, **kwargs)
            print(f"[LOG] {func.__name__} 执行成功, 返回: {result}")
            return result
        except Exception as e:
            print(f"[LOG] {func.__name__} 执行失败, 错误: {e}")
            raise
    return wrapper

def check_permission(required_level):
    """权限检查装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, user_level=None, **kwargs):
            if user_level is None or user_level < required_level:
                raise PermissionError(f"权限不足,需要等级 {required_level}")
            return func(*args, user_level=user_level, **kwargs)
        return wrapper
    return decorator

@log_execution
@check_permission(required_level=3)
@lru_cache(maxsize=128)
def get_sensitive_data(data_id, user_level=None):
    """获取敏感数据的函数"""
    # 模拟数据库查询
    data = {
        1: "机密数据 A",
        2: "机密数据 B",
        3: "机密数据 C"
    }
    return data.get(data_id, "未找到")

# 测试装饰器链
try:
    print(get_sensitive_data(1, user_level=5))  # 成功
    print(get_sensitive_data(2, user_level=1))  # 失败
except PermissionError as e:
    print(f"错误捕获: {e}")

五、装饰器的实际应用场景

1. 简单缓存装饰器

def simple_cache(max_size=100):
    """不依赖 functools 的简单缓存实现"""
    def decorator(func):
        cache = {}
        cache_order = []
        
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 创建缓存键
            key = (args, tuple(sorted(kwargs.items())))
            
            if key in cache:
                print(f"缓存命中: {func.__name__}{args}")
                return cache[key]
            
            # 缓存未命中,执行函数
            result = func(*args, **kwargs)
            
            # 添加到缓存
            cache[key] = result
            cache_order.append(key)
            
            # 如果超过最大大小,删除最旧的
            if len(cache_order) > max_size:
                oldest_key = cache_order.pop(0)
                del cache[oldest_key]
            
            return result
        
        # 清除缓存的方法
        wrapper.clear_cache = lambda: (cache.clear(), cache_order.clear())
        return wrapper
    
    return decorator

@simple_cache(max_size=5)
def calculate_pi(precision):
    """计算圆周率的简单示例"""
    print(f"计算圆周率,精度: {precision}")
    # 模拟耗时计算
    import math
    return round(math.pi, precision)

print(calculate_pi(5))  # 第一次计算
print(calculate_pi(5))  # 从缓存读取
print(calculate_pi(10)) # 第一次计算
calculate_pi.clear_cache()  # 清除缓存

2. 重试机制装饰器

import random

def retry(max_attempts=3, delay=1, backoff=2):
    """自动重试装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            current_delay = delay
            
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    if attempts == max_attempts:
                        raise Exception(f"重试 {max_attempts} 次后仍然失败: {e}")
                    
                    print(f"第 {attempts} 次尝试失败: {e}")
                    print(f"等待 {current_delay} 秒后重试...")
                    time.sleep(current_delay)
                    current_delay *= backoff
            
            return None
        return wrapper
    return decorator

@retry(max_attempts=3, delay=1, backoff=2)
def unstable_api_call():
    """模拟不稳定的 API 调用"""
    if random.random() < 0.7:  # 70% 概率失败
        raise ConnectionError("网络连接失败")
    return "API 调用成功"

print(unstable_api_call())

3. 单例模式装饰器

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 = True
    
    def query(self, sql):
        return f"执行查询: {sql}"

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

六、最佳实践和注意事项

1. 使用 functools.wraps

from functools import wraps

def proper_decorator(func):
    @wraps(func)  # 保留原函数的元数据
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@wraps(func) 会复制原函数的 __name____doc____module__ 等属性到装饰器函数,这对于调试和文档生成非常重要。

2. 避免修改被装饰函数的签名

如果装饰器需要改变函数签名,应该明确说明,或者使用更高级的工具如 wrapt 库。

3. 装饰器的性能考虑

装饰器会增加函数调用的开销。在性能敏感的场景中,应该谨慎使用,或者考虑使用类装饰器来减少闭包创建的成本。

七、总结

Python 装饰器是一个非常强大的工具,它可以帮助我们:

  • 减少代码重复:将横切关注点(如日志、缓存、权限)分离出来
  • 提高代码可读性:通过声明式的方式添加功能
  • 增强可维护性:功能修改只需调整装饰器,不影响业务逻辑

掌握装饰器的高级用法,可以让你的 Python 代码更加优雅和高效。在实际项目中,合理使用装饰器可以大幅提升代码质量和开发效率。

希望本文能够帮助你更好地理解和应用 Python 装饰器!

相关文章

[Python 教程] OpenCV-Python 入门:图像处理基础详解

OpenCV-Python 入门:图像处理基础详解OpenCV 是一个跨平台计算机视觉库,轻量级且高效,支持 Python 接口。本文将系统介绍 OpenCV 的核心概念和基础操作。一、OpenCV...

Python 装饰器实用指南:从入门到精通

装饰器本质上是一个接受函数作为参数并返回新函数的高阶函数。这个概念听起来有些抽象,但让我们通过一个具体的例子来理解它的实际价值。想象一下,你正在开发一个 Web 应用,需要记录每个函数的执行时间。如果...

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

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

深入理解 Python 装饰器:从基础到高级的完整指南

什么是装饰器?装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能。装饰器的返回值通常也是一个函数对象。这种设计模式遵循了"开放封闭原则"——对扩展开放,...

Python 上下文管理器的实战应用与原理深度解析

Python 上下文管理器的实战应用与原理深度解析 概述 上下文管理器是 Python 中一个优雅而强大的特性,通过 with 语句实现资源的自动管理。本文将从原理到实践,深入讲解如何创建自定义上下...

Python 类型提示实战指南:让代码更健壮

类型提示并不是强制执行的类型系统,而是一种可选的代码文档化工具。它通过注解函数参数和返回值的类型,帮助开发者更清晰地表达意图,同时让 IDE 和类型检查器(如 mypy)能够提前发现潜在的类型错误。...

发表评论

访客

看不清,换一张

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