直接用 pickle 或 json.dumps 处理万级节点树易卡死、爆内存或抛 RecursionError;应先转紧凑字典结构,再用 msgpack 编码,并避开递归调用栈。

直接用 pickle 或 json.dumps 处理万级节点以上的树,大概率会卡死、爆内存或抛 RecursionError。真正快且稳的方式是:先转为紧凑字典结构 + 用 msgpack 编码 + 避开递归调用栈。
为什么不能直接 json.dumps(TreeNode)?
Python 的 json.dumps 只认 dict、list、str、int 等内置类型。遇到自定义类(比如 TreeNode)会立刻报错:
TypeError: Object of type TreeNode is not JSON serializable
这不是配置问题,是机制限制。强行加 default=lambda o: o.__dict__ 也扛不住循环引用或含不可序列化字段(如 lambda、文件句柄、threading.Lock)的节点。
to_dict() 必须手写,且不能纯递归
最可控的方式是显式定义 to_dict() 方法,只取你需要的字段,并控制遍历方式:
立即学习“Python免费学习笔记(深入)”;
- 深度超 1000 层时,纯递归容易触发
RecursionError,改用栈模拟 DFS 或队列做 BFS - 子节点列表里有
None,必须显式保留,否则反序列化时无法还原空分支 - 如果节点带元信息(如
expanded: True、color: "blue"),每个子元素得是字典,不是裸值 - 避免在
to_dict()中访问动态属性(如self.parent),否则可能引入循环引用
msgpack.packb 比 json.dumps 快 5 倍以上,但要求输入是纯数据结构
msgpack 不接受自定义对象,只吃 dict/list/str/int 等。所以流程必须是:
TreeNode → to_dict() → dict → msgpack.packb()
实操建议:
- 用
dataclasses.asdict()替代手写to_dict(),前提是类已用@dataclass定义且无循环引用 - 对超大树(>5 万节点),分块序列化:按层级切片或按子树 ID 分组,用
concurrent.futures.ThreadPoolExecutor并行处理 - 序列化后加 CRC32 校验,防止传输损坏导致反序列化静默失败
- 别用
orjson—— 它虽快,但不支持bytes键或自定义编码逻辑,容错性不如msgpack
反序列化时 None 判断漏掉一个,就崩
从 msgpack.unpackb() 拿到字典后重建树,必须对每个子节点字段做显式 is not None 判断:
if data.get("left") is not None:
node.left = TreeNode.from_dict(data["left"])
漏判 None 会导致 AttributeError;用 data.get("left", {}) 更危险——空字典会被误建为有效节点。另外,若原始树用了 __slots__,反序列化构造实例时需绕过 setattr 限制,改用 object.__setattr__。


















