You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python3.11带参数装饰器:类方法全局事件总线调用报错问题

解决方案:处理事件总线中类方法的self参数问题

问题核心是:类定义时用@on装饰的方法是未绑定的普通函数,事件总线触发时直接调用这个函数,没有传入实例的self参数,导致报错。下面是两种无需反射的通用解决方案:


方案1:用描述符自动绑定实例方法

通过让装饰器返回一个描述符对象,在实例访问方法时自动完成绑定并注册到事件总线。这种方式可以保留装饰器的写法,同时正确处理self参数:

import weakref

# 全局事件总线存储
events = {}

class EventListener:
    def __init__(self, event_name, callback):
        self.event_name = event_name
        self.callback = callback
        # 用弱引用字典存储实例与绑定方法的映射,避免内存泄漏
        self.bound_callbacks = weakref.WeakKeyDictionary()

    def __get__(self, instance, owner):
        # 类访问时返回描述符自身
        if instance is None:
            return self
        # 实例访问时,绑定方法并注册到事件总线
        if instance not in self.bound_callbacks:
            bound_method = self.callback.__get__(instance, owner)
            events.setdefault(self.event_name, []).append(bound_method)
            self.bound_callbacks[instance] = bound_method
        return self.bound_callbacks[instance]

def on(event_name):
    def decorator(callback):
        return EventListener(event_name, callback)
    return decorator

def emit(event_name, *args, **kwargs):
    for callback in events.get(event_name, []):
        callback(*args, **kwargs)

# 使用示例
class Ticking:
    @on("clock")
    def on_clock(self, clock):
        print(f"Clock ticked: {clock}")

t = Ticking()
emit("clock", 123)  # 输出:Clock ticked: 123

说明:

  • 描述符的__get__方法会在实例访问该方法时被调用,此时能拿到实例对象instance,通过callback.__get__(instance, owner)生成绑定了self的实例方法。
  • 用weakref.WeakKeyDictionary存储绑定关系,当实例被垃圾回收时,对应的绑定方法会自动从字典中移除,避免内存泄漏。

方案2:在实例初始化时注册监听器

放弃类定义时的装饰器写法,改为在__init__中注册已经绑定了self的实例方法。这种方式更简单,无需修改装饰器:

events = {}

def on(event_name):
    def decorator(callback):
        events.setdefault(event_name, []).append(callback)
        return callback
    return decorator

def emit(event_name, *args, **kwargs):
    for callback in events.get(event_name, []):
        callback(*args, **kwargs)

# 使用示例
class Ticking:
    def __init__(self):
        # 注册当前实例的on_clock方法(已绑定self)
        on("clock")(self.on_clock)

    def on_clock(self, clock):
        print(f"Clock ticked: {clock}")

t = Ticking()
emit("clock", 123)  # 输出:Clock ticked: 123

说明:

  • 在__init__中注册的是已经绑定了实例的方法,事件总线触发时直接调用即可,不会缺少self参数。
  • 缺点是需要手动在每个实例的初始化方法中注册监听器,不如装饰器写法直观。

内容的提问来源于stack exchange,提问作者Jacob Jewett

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 17:42:49