Python单例类装饰器用于其他类方法触发TypeError的解决咨询
问题分析
你遇到的错误有两个核心原因:
- 实例方法调用方式错误:
register是EventDispatcher的实例方法,直接通过类名EventDispatcher.register调用时,Python会将你传入的"play_note"当作实例方法的self参数,导致缺少event_type参数,触发TypeError。 - 类方法绑定问题:即使解决调用问题,直接将类的未绑定方法注册到监听器,后续触发事件时调用该方法会缺少
self参数——未绑定方法必须接收实例作为第一个参数才能正常执行。
解决方案
以下提供两种可行的修改方案,均能让装饰器同时支持普通函数和类方法:
方案一:使用描述符延迟注册绑定方法
通过描述符机制,在类实例化时自动将绑定后的实例方法注册到事件系统,避免重复注册:
from functools import wraps from collections import defaultdict import inspect class EventDispatcher: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._listeners = defaultdict(list) return cls._instance @classmethod def register(cls, event_type: str): def decorator(func): # 处理普通函数:直接注册 if not inspect.isfunction(func): cls._instance._listeners[event_type].append(func) return func # 处理类方法:返回描述符,在实例化时完成绑定和注册 class EventMethodDescriptor: def __init__(self, func, event_type): self.func = func self.event_type = event_type self.method_name = None def __set_name__(self, owner, name): self.method_name = name def __get__(self, instance, owner): if instance is None: return self.func # 创建绑定到实例的方法 bound_method = self.func.__get__(instance, owner) # 注册到事件系统 cls._instance._listeners[self.event_type].append(bound_method) # 替换实例属性,避免重复注册 setattr(instance, self.method_name, bound_method) return bound_method return EventMethodDescriptor(func, event_type) return decorator # 使用示例 class MidiOutput: def __init__(self, out_port: str): self.out_port = out_port @EventDispatcher.register("play_note") def on_play_note(self, event): print(f"[{self.out_port}] 处理播放音符事件: {event}") # 测试触发事件 dispatcher = EventDispatcher() output1 = MidiOutput("Port1") output2 = MidiOutput("Port2") for listener in dispatcher._listeners["play_note"]: listener({"note": 60, "velocity": 100})
方案二:通过基类自动扫描注册
创建一个基类,在子类实例化时自动扫描带有标记的方法,完成绑定和注册:
from functools import wraps from collections import defaultdict import inspect class EventDispatcher: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._listeners = defaultdict(list) return cls._instance @classmethod def register(cls, event_type: str): def decorator(func): # 给方法添加事件类型标记 setattr(func, "_event_type", event_type) # 普通函数直接注册 if not inspect.isfunction(func): cls._instance._listeners[event_type].append(func) return func return decorator # 事件监听器基类:负责扫描并注册子类的事件方法 class EventListenerBase: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 扫描实例的所有方法,注册带标记的方法 for name, method in inspect.getmembers(self, predicate=inspect.ismethod): original_func = method.__func__ if hasattr(original_func, "_event_type"): event_type = getattr(original_func, "_event_type") EventDispatcher._instance._listeners[event_type].append(method) # 使用示例 class MidiOutput(EventListenerBase): def __init__(self, out_port: str): super().__init__() self.out_port = out_port @EventDispatcher.register("play_note") def on_play_note(self, event): print(f"[{self.out_port}] 处理播放音符事件: {event}") # 测试触发事件 dispatcher = EventDispatcher() output1 = MidiOutput("Port1") output2 = MidiOutput("Port2") for listener in dispatcher._listeners["play_note"]: listener({"note": 60, "velocity": 100})
内容的提问来源于stack exchange,提问作者user20617512
相关产品推荐
相关产品推荐

