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

Python类中字典条目消失问题排查求助

问题分析:NetMessageHandler回调字典为空的原因

问题描述

定义了NetMessageHandler类,包含onmessage()、rcvtcpmsg()及_run_callbacks()方法:

  • onmessage()用于注册特定网络消息的回调函数
  • rcvtcpmsg()由TCP客户端调用,解析消息后触发回调
  • _run_callbacks()执行对应消息类型的回调

调用onmessage()时,回调成功存入_callbacks字典,打印结果符合预期:

{'systemdescription': [<function system_description_handler at 0x7f70a57ee0>]}
Length: 1

但触发回调时,_run_callbacks()中的_callbacks字典为空,导致回调无法执行,且未对字典执行清空或重新赋值操作,回调是在第一条消息到达前添加的。

相关类代码:

class NetMessageHandler():
    def __init__(self):
        # 创建空字典存储回调
        self._callbacks = {}

    def _run_callbacks(self, type: str, data: dict[str, Any], origin: NetMessageOrigin):
        '''收到消息时执行回调'''
        print(f'{self.__class__.__name__}: 为{type}执行回调')
        print(self._callbacks)  # 测试打印,返回空字典: '{}'
        
        # 因字典为空,此部分永远不会执行
        if type in self._callbacks:
            for c in self._callbacks[type]:
                c(type, data, origin)
   
    def rcvtcpmsg(self, msg: str, origin: 'TCPClient') -> None:
        '''接收TCP消息并解析为JSON,然后执行回调'''
        data = self._parseMessage(msg)

        # 创建代表消息发送方的origin对象
        origin = NetMessageOrigin(NetMessageOriginType.TCP, origin)

        # 针对特定消息类型执行回调
        if "t" in data:
            self._run_callbacks(data["t"], data, origin)

    def onmessage(self, type:str, callback:Callable[[str, dict[str, Any], NetMessageOrigin], Any]):
        '''为特定消息类型添加新回调'''
        # 检查该消息类型的回调列表是否已存在
        if type not in self._callbacks:
            print(f'{self.__class__.__name__}: 为"{type}"创建新回调数组')
            self._callbacks[type] = []

        if callback not in self._callbacks[type]:
            self._callbacks[type].append(callback)
            print(f'{self.__class__.__name__}: 为消息类型"{type}"添加回调')
        else:
            print(f'{self.__class__.__name__}: 消息类型"{type}"的回调已存在')
        
        # 以下打印输出预期值: {'systemdescription': [<function system_description_handler at 0x7f70a57ee0>]}
        print(self._callbacks)
        print("Length:", len(self._callbacks))

核心原因:使用了多个NetMessageHandler实例

这是此类问题最常见的根源:

  • 调用onmessage()注册回调时,用的是A实例,该实例的_callbacks字典确实存储了回调
  • 但TCP客户端调用rcvtcpmsg()时,用的是B实例,这个新实例的_callbacks是初始化后的空字典,自然找不到回调

Python中每个类实例的属性都是独立的,不同实例的self._callbacks完全是两个不同的字典,互相不影响。

验证方法

在onmessage()和_run_callbacks()中添加实例ID打印,确认是否为同一个实例:

# 在onmessage()末尾添加
print(f"实例ID (注册回调): {id(self)}")

# 在_run_callbacks()开头添加
print(f"实例ID (执行回调): {id(self)}")

如果两次打印的ID不同,就实锤了是实例不统一的问题。

修复方案

确保注册回调和接收消息使用同一个NetMessageHandler实例:

  • 全局单例模式:如果整个应用只需要一个消息处理器,可以把NetMessageHandler实现为单例
    class NetMessageHandler():
        _instance = None
        
        def __new__(cls):
            if cls._instance is None:
                cls._instance = super().__new__(cls)
                cls._instance._callbacks = {}
            return cls._instance
    
  • 显式传递实例:在初始化TCP客户端时,把已注册好回调的NetMessageHandler实例传递给它,让客户端调用该实例的rcvtcpmsg()方法
  • 依赖注入:通过构造函数或参数传递,确保所有相关模块使用同一个实例

其他排查方向

如果实例是同一个,再检查以下情况:

  • 是否有其他代码(比如异常处理、异步逻辑)意外重置了self._callbacks
  • 多线程环境下是否存在竞态条件(比如一个线程在添加回调,另一个线程在修改字典,但你说没执行清空操作,概率较低)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 13:40:56