欢迎光临
我们一直在努力

如何利用装饰器实现带状态的函数闭包与类方法属性拦截教程

作为Python的高级特性之一,装饰器(Decorator)不仅可以用来修改或增强函数的行为,更强大的功能在于通过使用类来实现装饰器,从而实现状态的保持(Stateful Closure)和类方法的属性拦截(Attribute Interception)。

1. 为什么需要带状态的装饰器?

传统的函数式装饰器在每次函数调用之间不会保留状态。如果我们需要记录某个函数被调用的次数、计算平均执行时间,或者实现简单的限流逻辑,我们就需要一个可以持久化状态的机制。实现这一点的最佳方式是使用一个类作为装饰器。

2. 实现状态保持的装饰器 (Call Counter)

我们将创建一个名为 CallTracker 的类,它将记录被装饰函数或方法被调用的总次数。

class CallTracker:
    def __init__(self, func):
        # 存储原始函数
        self.func = func
        # 状态:记录调用次数
        self.call_count = 0
        print(f"[初始化]: {func.__name__} 的追踪器已创建")

    def __call__(self, *args, **kwargs):
        # 每次调用时更新状态
        self.call_count += 1
        print(f"--- {self.func.__name__} (总调用次数: {self.call_count}) ---")
        # 执行原始函数
        return self.func(*args, **kwargs)

# 示例:装饰普通函数
@CallTracker
def add(a, b):
    return a + b

print(f"结果: {add(1, 2)}")
print(f"结果: {add(3, 4)}")
print(f"总调用次数(通过访问装饰器实例属性): {add.call_count}")

输出结果:

[初始化]: add 的追踪器已创建
--- add (总调用次数: 1) ---
结果: 3
--- add (总调用次数: 2) ---
结果: 7
总调用次数(通过访问装饰器实例属性): 2

3. 如何解决类方法拦截与绑定问题 (描述符协议)

当我们将上述 CallTracker 应用到类方法上时,会遇到一个问题:类方法需要正确地“绑定”到实例(即自动接收 self 参数)。

当一个装饰器类被用作方法时,它实际上成为了一个描述符(Descriptor)。要让它正确地工作并接收实例的引用,我们需要实现描述符协议中的核心方法 __get__

__get__(self, instance, owner) 方法的作用是定义当属性被访问时应该返回什么。

  • instance: 访问属性的那个对象实例(如果通过实例访问)。
  • owner: 拥有该属性的类。

我们修改 CallTracker,使其支持方法绑定:

class MethodCallTracker:
    def __init__(self, func):
        self.func = func
        # 使用字典存储每个实例的调用计数,实现“状态分层”
        self.counts = {}

    def __get__(self, instance, owner):
        if instance is None:
            # 通过类访问时,返回自身
            return self

        # 通过实例访问时,返回一个“绑定”到该实例的新函数闭包
        def bound_method(*args, **kwargs):
            # 初始化或更新该实例的调用计数
            instance_id = id(instance)
            self.counts[instance_id] = self.counts.get(instance_id, 0) + 1

            count = self.counts[instance_id]
            print(f"[实例ID:{instance_id}] 方法 {self.func.__name__} 被调用 {count} 次")

            # 执行原始方法,确保将 instance (即 self) 作为第一个参数传入
            return self.func(instance, *args, **kwargs)

        return bound_method

# 使用这个进阶装饰器
class DatabaseConnection:
    def __init__(self, name):
        self.name = name

    @MethodCallTracker
    def fetch_data(self, query):
        print(f"-> {self.name} 正在执行查询: {query}")
        return [1, 2, 3]

# 创建两个独立的实例
db1 = DatabaseConnection("DB_Prod")
db2 = DatabaseConnection("DB_Test")

db1.fetch_data("SELECT A")
db2.fetch_data("SELECT B")
db1.fetch_data("SELECT C")

输出结果分析:

[实例ID:4728519632] 方法 fetch_data 被调用 1 次
-> DB_Prod 正在执行查询: SELECT A
[实例ID:4728520240] 方法 fetch_data 被调用 1 次
-> DB_Test 正在执行查询: SELECT B
[实例ID:4728519632] 方法 fetch_data 被调用 2 次
-> DB_Prod 正在执行查询: SELECT C

通过实现 __get__ 方法,我们成功地将状态追踪逻辑(self.counts)绑定到了类实例的生命周期上,从而实现了基于装饰器的类方法属性拦截和状态分离

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » 如何利用装饰器实现带状态的函数闭包与类方法属性拦截教程
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址