pickle能直接保存嵌套字典、列表、元组、集合、datetime、decimal、numpy.ndarray及模块顶层定义的自定义类实例和函数,但不可序列化文件句柄、线程锁、lambda函数等对象。

pickle 能序列化绝大多数 Python 对象,但“能存”不等于“一定能正确还原”——关键取决于对象是否可封存(picklable),以及你是否处理了类定义、模块路径、协议版本等隐性依赖。
哪些复杂结构能直接用 pickle.dump() 保存?
只要对象不包含不可序列化的成分(如文件句柄、线程锁、lambda 函数、某些 C 扩展对象),pickle 就能处理:
- 嵌套字典 + 列表 + 元组 + 集合(含混合类型)
- 自定义类的实例(前提是类定义在可导入的模块中,且未动态修改
__dict__) -
datetime、decimal、numpy.ndarray(需对应库已安装) - 函数(仅限模块顶层定义的函数,不支持闭包或 lambda)
典型失败场景:class A: pass; a = A(); a.__dict__['fd'] = open('/tmp/x', 'w') —— 因含文件对象,pickle.dump(a, f) 会报 TypeError: cannot pickle '_io.TextIOWrapper' object。
为什么自定义类实例有时反序列化失败?
不是对象本身问题,而是类定义缺失或路径不一致。例如:
class Config:
def __init__(self):
self.host = "localhost"
若你在脚本 A 中定义并保存了 Config(),却在脚本 B 中尝试 pickle.load(),而 B 没导入 Config 或导入路径不同(比如从 mylib.config 改成 config),就会抛出 AttributeError: Can't get attribute 'Config' on <module></module>。
解决方法只有两个:
- 确保加载端能通过相同 import 路径访问到该类(推荐把类定义放在独立模块里,统一 import)
- 重写
__reduce__方法显式指定构造逻辑(适合无法控制类定义位置的场景)
pickle.dump() 和 pickle.dumps() 的关键区别在哪?
本质是目标介质不同,但影响远不止于此:
-
pickle.dump(obj, file):必须用'wb'模式打开文件;适合持久化到磁盘 -
pickle.dumps(obj):返回bytes,不涉及 I/O;适合网络传输、缓存到 Redis、或做内存级快照 - 二者默认使用最高协议(Python 3.8+ 是 protocol 5),但跨版本兼容时需显式指定低版本协议,比如
pickle.dump(obj, f, protocol=4)
注意:如果对象很大(>100MB),dumps() 会一次性分配内存,可能触发 MemoryError;此时应坚持用 dump() 流式写入。
如何避免 unpickle 时执行恶意代码?
pickle 反序列化本质上是执行任意 Python 字节码,这是它最危险的特性。没有“安全模式”,只有规避策略:
- 绝对不 unpickle 来自用户上传、网络请求、数据库直读的未知数据
- 若必须接收外部 pickle 数据,先用
hmac校验完整性(发送方签名,接收方验证) - 生产环境优先用
json+ 类型转换(如datetime.isoformat())替代,除非明确需要保留对象行为 - 临时调试时,可用
ast.literal_eval()替代loads()处理简单结构,但它不支持任何函数调用或类实例
真正容易被忽略的点是:即使你控制了数据来源,如果部署环境的 Python 版本、第三方库版本、甚至模块搜索路径发生变化,也可能导致 unpickle 失败——这不是 bug,而是 pickle 的设计使然:它保存的是“重建指令”,而非“数据快照”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











