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

Python单例类装饰器用于其他类方法触发TypeError的解决咨询

问题分析

你遇到的错误有两个核心原因:

  1. 实例方法调用方式错误:register是EventDispatcher的实例方法,直接通过类名EventDispatcher.register调用时,Python会将你传入的"play_note"当作实例方法的self参数,导致缺少event_type参数,触发TypeError。
  2. 类方法绑定问题:即使解决调用问题,直接将类的未绑定方法注册到监听器,后续触发事件时调用该方法会缺少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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 16:55:17