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
相关产品推荐
相关产品推荐

