如何修复基于装饰器的事件系统中类方法调用的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
相关产品推荐
相关产品推荐

