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

Python 装饰器的高级用法与设计模式

admin2周前 (03-24)Python21

Python 装饰器(Decorator)是语言中最优雅的特性之一,它允许我们以声明式的方式修改或增强函数的行为。本文将深入探讨装饰器的高级用法,包括带参数的装饰器、类装饰器、装饰器链、以及实际开发中的设计模式应用。

一、装饰器基础回顾

装饰器的本质是一个接受函数作为参数并返回新函数的高阶函数。最简单的装饰器示例:

import time

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

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

slow_function()  # 输出: slow_function 耗时: 0.1001秒

但基础的装饰器有一些问题:丢失了原函数的元数据(__name__, __doc__等),且无法接受参数。让我们逐步解决这些问题。

二、保持函数元数据

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

from functools import wraps

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

@log_call
def calculate(x, y):
    """计算两个数的和"""
    return x + y

print(calculate.__name__)  # 输出: calculate
print(calculate.__doc__)   # 输出: 计算两个数的和

三、带参数的装饰器工厂

当装饰器需要接受参数时,我们需要使用三层嵌套:外层接收参数,中层返回装饰器,内层包装函数:

def repeat(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                results.append(func(*args, **kwargs))
            return results[-1]  # 返回最后一次调用的结果
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"你好, {name}!")
    return f"greeted_{name}"

greet("小明")  # 输出三遍问候

这种模式被称为"装饰器工厂"——它创建并返回真正的装饰器。

四、类装饰器

装饰器也可以用于类。类装饰器可以动态修改类的属性或方法:

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

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

user = User("张三", 25)
print(user)  # 输出: User({'name': '张三', 'age': 25})

类装饰器常用于注册类(如 ORM 模型注册)、添加通用方法、或修改类行为。

五、装饰器链与执行顺序

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

def uppercase(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper() if isinstance(result, str) else result
    return wrapper

def exclamation(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return f"{result}!"
    return wrapper

@exclamation
@uppercase
def say_hello():
    return "hello world"

print(say_hello())  # 输出: HELLO WORLD!
# 执行顺序: say_hello -> uppercase -> exclamation

理解装饰器链的顺序对调试装饰器行为至关重要。

六、单例模式装饰器

装饰器是实现设计模式的利器。下面是一个线程安全的单例模式:

import threading

def singleton(cls):
    instances = {}
    lock = threading.Lock()
    
    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            with lock:
                if cls not in instances:  # 双重检查锁定
                    instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    
    return get_instance

@singleton
class Database:
    def __init__(self):
        print("创建数据库连接")
        self.connection = "模拟连接"

db1 = Database()
db2 = Database()
print(db1 is db2)  # 输出: True

这个实现使用了双重检查锁定(Double-Checked Locking)确保线程安全。

七、缓存装饰器

Python 的 functools.lru_cache 是内置的缓存装饰器,我们也可以实现自定义版本:

def memoize(max_size=128):
    """带大小限制的记忆化装饰器"""
    def decorator(func):
        cache = {}
        @wraps(func)
        def wrapper(*args, **kwargs):
            key = (args, tuple(sorted(kwargs.items())))
            if key in cache:
                return cache[key]
            if len(cache) >= max_size:
                cache.popitem()  # 删除最旧的条目
            result = func(*args, **kwargs)
            cache[key] = result
            return result
        wrapper.cache = cache  # 暴露缓存用于调试
        return wrapper
    return decorator

@memoize(max_size=3)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(100))  # 快速计算大数

这种记忆化技术可以将递归算法的时间复杂度从 O(2^n) 降到 O(n)。

八、权限验证装饰器

在 Web 开发中,装饰器常用于权限检查:

def require_permission(permission):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            current_user = kwargs.get('user')
            if not current_user or permission not in current_user.permissions:
                raise PermissionError(f"需要权限: {permission}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

class User:
    def __init__(self, permissions):
        self.permissions = permissions

@require_permission('admin')
def delete_user(user_id, user=None):
    print(f"删除用户 {user_id}")

admin = User(['admin', 'editor'])
delete_user(123, user=admin)  # 正常执行

guest = User(['guest'])
# delete_user(456, user=guest)  # 抛出 PermissionError

九、装饰器的性能考虑

装饰器会引入额外的函数调用开销。对于性能敏感的场景,可以考虑:

import functools

def fast_timer(func):
    """使用 functools.partial 减少闭包开销"""
    @functools.wraps(func)
    def wrapper(*args, _func=func, **kwargs):
        start = time.perf_counter()
        result = _func(*args, **kwargs)
        print(f"耗时: {time.perf_counter() - start:.4f}")
        return result
    return wrapper

十、最佳实践总结

1. 始终使用 functools.wraps 保留函数元数据

2. 装饰器应该保持透明——包装函数的签名应与原函数一致

3. 避免在装饰器中修改全局状态

4. 考虑使用类作为装饰器,当装饰器需要维护状态时

5. 装饰器链的顺序很重要:靠近函数的装饰器先执行

6. 对于简单场景,优先考虑使用内置装饰器如 @property、@staticmethod

装饰器是 Python 元编程的核心工具之一。掌握高级装饰器技巧,可以写出更简洁、更可维护、更 Pythonic 的代码。在实际项目中,合理使用装饰器可以显著提升代码的可读性和复用性。

相关文章

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

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

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

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

Python 上下文管理器实战:从 with 语句到自定义资源管理

在 Python 编程中,上下文管理器(Context Manager)是一个强大但常被低估的特性。当你使用 open() 函数读取文件时,那个熟悉的 with 语句背后,正是上下文管理器在默默工作。...

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

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

Python异步编程入门与实战

异步编程是一种并发执行的编程模式,它允许程序在等待耗时操作(如网络请求、文件读写)时,继续执行其他任务。Python 3.5引入了async/await语法,使得异步编程变得更加直观和易于理解。为什么...

Python 多线程基础与 ThreadPoolExecutor 实战

在 Python 中,多线程是实现并发的一种常见方式,尤其在处理 I/O 密集型任务时效果明显。本文从线程的基本概念入手,演示如何使用 threading.Thread 模块创建线程,并对比使用 Th...

发表评论

访客

看不清,换一张

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