pickle天然支持深度持久化,因其默认跟踪对象引用、处理嵌套结构、循环引用、递归及自定义类实例,能完整封存并重建对象图,但不保证跨版本兼容且存在安全风险。

pickle 能做到深度持久化——但不是靠“加参数”,而是靠它默认就支持嵌套、引用、递归和自定义类实例。只要你没手动破坏对象图结构,它就能原样存、原样读回来。
为什么 pickle.dump() 和 pickle.load() 天然支持深度序列化
pickle 的核心机制是跟踪对象引用,不是简单地 flatten 字段。比如一个包含循环引用的树节点、一个带方法绑定的实例、或者字典里嵌套了 set 再嵌套了自定义对象——只要这些对象在 Python 运行时可被访问(即类定义在模块里可 import),pickle 就能完整封存整个对象图。
- 它会自动处理
list里套dict套set套datetime套自定义类实例 - 对同一对象的多次引用(如
a = [obj, obj])不会重复序列化,解封后仍是同一内存地址(is成立) - 递归结构(如
node.parent = node)也能安全封存,不会栈溢出或崩溃
注意:pickle 不保存对象的内存地址,而是重建等价结构;反序列化后的对象与原对象 == 成立,但 is 不成立(除非你用 copy.deepcopy 那种语义,而它本来就是 deep copy 行为)。
pickle.dumps() 和 pickle.loads() 在内存中做深度序列化的实操要点
这两个函数适合缓存、RPC、进程间通信等不落地场景,但容易因忽略协议版本或编码方式出错:
-
pickle.dumps(obj)默认用 protocol=3(Python 3.8+),但如果你要兼容旧环境(比如 Python 3.6),显式传protocol=4可能反而失败——protocol=4 是 3.8+ 才支持的 - 返回值一定是
bytes,别试图用str处理或写入文本文件 -
pickle.loads()输入必须是bytes,传str会报TypeError: a bytes-like object is required - 如果对象含不可序列化成分(比如
lambda、打开的文件句柄、线程锁),dumps()会直接抛AttributeError或PicklingError
常见错误示例:
import pickle
obj = {'data': lambda x: x * 2}
pickle.dumps(obj) # 报错:Can't pickle <function> at 0x...></function>
自定义类如何确保深度持久化不丢状态
默认情况下,pickle 会保存实例的 __dict__,但如果你重写了 __slots__、用了属性描述符、或有非 __dict__ 存储的状态(比如 C 扩展对象),就得手动干预:
- 重写
<strong>getstate</strong>():返回你想持久化的状态字典(比如过滤掉临时计算字段) - 重写
<strong>setstate</strong>():从字典恢复实例状态,必要时初始化未序列化的属性 - 若类依赖外部模块或动态生成,确保反序列化时该模块仍可导入(否则
ModuleNotFoundError)
典型场景:
- 机器学习模型训练完想保存权重和配置,但不想存 optimizer 状态 → 在
__getstate__里 pop 掉'optimizer' - 某个类用
__slots__节省内存 → 必须在__getstate__中显式构造状态字典,否则__dict__为空导致数据丢失
深度持久化的最大陷阱:跨 Python 版本和不可信数据
深度 ≠ 安全,也不等于长期兼容:-
pickle协议不保证跨大版本兼容(比如 Python 3.9 dump 的数据,在 3.12 load 可能失败),尤其涉及内置类型内部变更(如dict实现优化) - 反序列化任意
bytes等同于执行任意代码——攻击者可以构造恶意 payload,在pickle.load()时触发os.system()或远程加载模块 - 生产环境绝对不要对用户上传的
.pkl文件、网络接收的bytes直接调用loads()
真正需要深度持久化的场景,往往也意味着你控制着序列化和反序列化两端。这时候,把协议版本写死、加一层 hmac 签名校验、并限定只允许特定类名被反序列化,比幻想“一次封存,永久可用”更实际。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











