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

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

admin2周前 (03-24)Python19

装饰器是 Python 中最优雅的语法特性之一,它允许我们在不修改原始函数代码的情况下,为函数添加额外的功能。本文将从实际应用场景出发,深入探讨装饰器的高级用法。

基础回顾

装饰器的本质是一个接受函数作为参数,并返回一个新函数的高阶函数。最简单的装饰器可以这样写:

def timer(func):
    import time
    
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 执行时间: {end - start:.4f}秒")
        return result
    
    return wrapper

@timer
def slow_function():
    import time
    time.sleep(0.5)
    return "完成"

带参数的装饰器

当装饰器需要接受自定义参数时,我们需要再包裹一层函数:

def retry(max_attempts=3, delay=1):
    def decorator(func):
        import time
        
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    if attempts == max_attempts:
                        raise
                    print(f"第 {attempts} 次失败,{delay} 秒后重试...")
                    time.sleep(delay)
        
        return wrapper
    return decorator

@retry(max_attempts=2, delay=0.5)
def unstable_api():
    import random
    if random.random() > 0.7:
        raise Exception("API 暂时不可用")
    return "API 响应成功"

类装饰器

使用类作为装饰器可以更好地维护状态:

class CacheDecorator:
    def __init__(self, max_size=100):
        self.cache = {}
        self.max_size = max_size
        self.access_order = []
    
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            # 创建缓存键
            key = (args, frozenset(kwargs.items()))
            
            if key in self.cache:
                return self.cache[key]
            
            result = func(*args, **kwargs)
            
            # 更新缓存
            if len(self.cache) >= self.max_size:
                oldest = self.access_order.pop(0)
                del self.cache[oldest]
            
            self.cache[key] = result
            self.access_order.append(key)
            
            return result
        
        return wrapper

@CacheDecorator(max_size=50)
def expensive_calculation(n):
    print(f"计算 {n} 的斐波那契数...")
    if n <= 1:
        return n
    return expensive_calculation(n-1) + expensive_calculation(n-2)

装饰器堆叠

多个装饰器可以堆叠使用,执行顺序从内到外:

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

def validate_types(*type_args):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i, (arg, expected_type) in enumerate(zip(args, type_args)):
                if not isinstance(arg, expected_type):
                    raise TypeError(f"参数 {i} 应该是 {expected_type}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

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

保留原函数元数据

使用 functools.wraps 可以保留原函数的元信息:

import functools

def admin_required(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 检查权限逻辑
        print("检查管理员权限...")
        return func(*args, **kwargs)
    return wrapper

@admin_required
def delete_user(user_id):
    """删除指定用户"""
    return f"用户 {user_id} 已删除"

# 现在 delete_user.__name__ 和 __doc__ 都能正确获取

实战应用:权限控制装饰器

from functools import wraps

class PermissionDenied(Exception):
    pass

def require_permission(permissions):
    """权限检查装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            user = kwargs.get('user')
            if not user or not any(p in user.get('permissions', []) for p in permissions):
                raise PermissionDenied(f"需要权限: {permissions}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

# 使用示例
class UserService:
    @require_permission(['user:read'])
    def get_user(self, user_id, user):
        return {"id": user_id, "name": "测试用户"}
    
    @require_permission(['user:write'])
    def update_user(self, user_id, data, user):
        return {"id": user_id, **data}

实战应用:性能监控装饰器

import time
import functools
from collections import defaultdict

class PerformanceMonitor:
    def __init__(self):
        self.stats = defaultdict(list)
    
    def track(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            result = func(*args, **kwargs)
            end = time.perf_counter()
            duration = end - start
            self.stats[func.__name__].append(duration)
            return result
        return wrapper
    
    def get_stats(self):
        report = {}
        for func_name, times in self.stats.items():
            report[func_name] = {
                'total_calls': len(times),
                'avg_time': sum(times) / len(times),
                'max_time': max(times),
                'min_time': min(times)
            }
        return report

# 使用示例
monitor = PerformanceMonitor()

@monitor.track
def process_data(n):
    import random
    time.sleep(random.uniform(0.01, 0.1))
    return n * 2

总结

装饰器是 Python 中强大的代码复用工具。通过合理使用装饰器,我们可以:

1. 保持代码简洁和可维护性

2. 实现横切关注点(日志、缓存、权限等)

3. 提高代码的可读性和复用性

在实际项目中,建议将常用装饰器整理到单独的模块中,并配合 functools.wraps 使用,以保持良好的代码质量。

相关文章

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

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

Python装饰器完全指南:从基础到高级应用

装饰器是 Python 中最强大也最容易被误解的特性之一。很多初学者听说过装饰器,但总是感觉云里雾里,不敢在实际项目中使用。本文从最基础的概念讲起,逐步深入到高级应用场景,通过大量原创示例代码帮助...

Python 中利用 functools.lru_cache 实现高效缓存:从入门到进阶

Python 中利用 functools.lru_cache 实现高效缓存:从入门到进阶 在日常 Python 开发中,我们经常会遇到重复计算相同输入的问题,比如递归计算斐波那契数列、多次调用相同参...

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

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

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

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

Python 上下文管理器实战指南:优雅处理资源的艺术

# Python 上下文管理器实战指南:优雅处理资源的艺术 在 Python 编程中,资源的获取与释放是一个永恒的主题。文件操作、数据库连接、网络请求、锁的获取...这些场景都遵循相同的模式:打开资...

发表评论

访客

看不清,换一张

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