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

如何修复基于装饰器的事件系统中类方法调用的TypeError问题

问题修复方案

问题根源在于:类中的实例方法被装饰器注册时,存入_event_handlers的是未绑定的函数对象(属于类的方法),调用这类函数必须手动传入self参数,但dispatch_event直接调用时没有传递,因此抛出TypeError。我们需要将实例的绑定方法(已关联实例自身的方法)注册到事件系统中,而非未绑定的函数。

以下是三种可行的修复方案:

方案一:使用元类自动注册实例绑定方法

通过自定义元类,在实例化类对象时,自动将所有被@add_event_handler装饰的实例方法的绑定版本注册到事件系统中:

from collections import defaultdict

_event_handlers = defaultdict(set)


def add_event_handler(event_name=""):
    def wrapper(func):
        # 给被装饰的方法标记事件名
        setattr(func, "_event_name", event_name if event_name else func.__name__)
        return func
    return wrapper


# 自定义元类,用于自动注册实例绑定方法
class EventMeta(type):
    def __call__(cls, *args, **kwargs):
        # 创建实例
        instance = super().__call__(*args, **kwargs)
        # 遍历类中所有方法,找到被add_event_handler装饰的方法
        for name, attr in cls.__dict__.items():
            if callable(attr) and hasattr(attr, "_event_name"):
                event_name = getattr(attr, "_event_name")
                # 获取实例的绑定方法并注册
                bound_method = getattr(instance, name)
                _event_handlers[event_name].add(bound_method)
        return instance


@add_event_handler()
def bar():
    print("test")


class Foo(metaclass=EventMeta):
    def __init__(self, f):
        self.f = f

    @add_event_handler()
    def bar(self):
        print(self.f)


def dispatch_event(event_name, **kwargs):
    for handler in _event_handlers[event_name]:
        handler(**kwargs)


test = Foo(5)
# 此时test的bar绑定方法已自动注册
dispatch_event("bar")
# 输出:
# test
# 5

方案二:手动注册实例绑定方法

如果不想使用元类,也可以在实例化后手动将绑定方法注册到事件系统中:

from collections import defaultdict

_event_handlers = defaultdict(set)


def add_event_handler(event_name=""):
    def wrapper(func):
        event_name_final = event_name if event_name else func.__name__
        _event_handlers[event_name_final].add(func)
        return func
    return wrapper


@add_event_handler()
def bar():
    print("test")


class Foo:
    def __init__(self, f):
        self.f = f

    def bar(self):
        print(self.f)


def dispatch_event(event_name, **kwargs):
    for handler in _event_handlers[event_name]:
        handler(**kwargs)


test = Foo(5)
# 手动注册test实例的bar绑定方法
add_event_handler("bar")(test.bar)

dispatch_event("bar")
# 输出:
# test
# 5

方案三:使用描述符自动注册(首次访问方法时触发)

通过将装饰器实现为描述符,当实例首次访问被装饰的方法时,自动注册绑定方法:

from collections import defaultdict
import weakref

_event_handlers = defaultdict(set)


class add_event_handler:
    def __init__(self, event_name=""):
        self.event_name = event_name
        self.func = None
        # 使用弱引用集合保存已注册的实例,避免内存泄漏
        self._registered_instances = weakref.WeakSet()

    def __call__(self, func):
        self.func = func
        return self

    def __get__(self, instance, owner):
        if instance is None:
            return self.func
        # 获取绑定方法
        bound_method = self.func.__get__(instance, owner)
        # 仅注册一次该实例的方法
        if instance not in self._registered_instances:
            event_name = self.event_name if self.event_name else self.func.__name__
            _event_handlers[event_name].add(bound_method)
            self._registered_instances.add(instance)
        return bound_method


@add_event_handler()
def bar():
    print("test")


class Foo:
    def __init__(self, f):
        self.f = f

    @add_event_handler()
    def bar(self):
        print(self.f)


def dispatch_event(event_name, **kwargs):
    for handler in _event_handlers[event_name]:
        handler(**kwargs)


test = Foo(5)
# 首次访问test.bar触发注册
test.bar  # 或者调用一次test.bar()也会触发
dispatch_event("bar")
# 输出:
# test
# 5

说明

  • 方案一最自动化,适合需要自动注册所有实例方法的场景,无需额外手动操作。
  • 方案二最简单直观,适合对实例有明确控制的场景。
  • 方案三延迟注册,只有当实例方法被访问时才注册,适合实例创建后不一定会触发事件的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 17:15:16