pickle 把 Python 对象图转换为字节流,并在兼容的 Python 环境中恢复对象。它可以保留列表之间的共享引用、循环引用,以及许多 Python 类型,适合来源可信、环境受控的中间结果和缓存。
只反序列化可信且未被篡改的 pickle 数据。 加载过程可能执行对象恢复逻辑,恶意数据能够触发任意代码执行;捕获异常、检查 .pkl 后缀或改用另一个 pickle 协议都不能消除这个边界。Python pickle 文档
本文示例使用 Python 3.11,所有加载的字节都在同一示例中由程序自身生成。
内存字节与二进制文件
跳转到“内存字节与二进制文件”| 接口 | 作用 |
|---|---|
pickle.dumps(obj, protocol=...) | 返回表示对象的 bytes |
pickle.loads(data) | 从字节内容恢复对象 |
pickle.dump(obj, file, protocol=...) | 向二进制文件写入表示 |
pickle.load(file) | 从二进制文件读取一个表示并恢复对象 |
dump、load 使用 wb、rb 等二进制模式。读取时会自动识别协议版本,不需要再传 protocol。
下面先生成数据,再写入临时目录、读取并核对结果。版本字段表示应用的数据布局版本,与 pickle 协议号不是一回事。
import picklefrom pathlib import Pathfrom tempfile import TemporaryDirectory
data = { "schema_version": 1, "samples": [1.5, 2.0, 3.25], "shape": (3,), "flags": {"calibrated", "complete"},}
blob = pickle.dumps(data, protocol=5)assert isinstance(blob, bytes)restored = pickle.loads(blob)assert restored == dataassert isinstance(restored["shape"], tuple)assert isinstance(restored["flags"], set)
with TemporaryDirectory() as directory: path = Path(directory) / "measurements.pkl" with path.open("wb") as file: pickle.dump(data, file, protocol=5) with path.open("rb") as file: loaded = pickle.load(file) assert loaded["schema_version"] == 1 assert loaded == data程序自己曾经写过一个文件,不代表这个路径此后永远可信;实际使用中还要清楚谁能替换文件。外部接口传入的普通数据通常适合按约定使用 JSON 等数据格式,再验证字段。
共享关系和循环关系会被恢复
跳转到“共享关系和循环关系会被恢复”恢复的是一个新的对象图,不是原进程的内存地址。图内两个位置共同引用一个列表的关系,可以继续成立。
import pickle
shared = [1, 2]source = {"left": shared, "right": shared}source["self"] = sourcerestored = pickle.loads(pickle.dumps(source, protocol=5))
assert restored is not sourceassert restored["left"] is restored["right"]assert restored["left"] is not sharedassert restored["self"] is restored
restored["left"].append(3)assert restored["right"] == [1, 2, 3]assert shared == [1, 2]这与 deepcopy 的 memo 机制 有相似目的,但不能据此把 pickle 往返当作所有对象都适用的深拷贝:两者都有各自的对象协议、支持范围和执行行为。
协议兼容与代码依赖分别检查
跳转到“协议兼容与代码依赖分别检查”Python 3.11 支持协议 0–5,默认协议是 4;协议 5 从 Python 3.8 开始提供。显式写 protocol=5 表明读取环境至少要支持它。HIGHEST_PROTOCOL 表示当前解释器支持的最高协议,不能据此保证旧环境也能读取。数据流格式
协议匹配只是一个条件。自定义类通常按“模块名和类名”定位,pickle 文件不自动包含整个类定义、依赖包和源代码。函数通常也按可导入名称引用,而不是把函数体打包进去;局部函数和普通 lambda 不适合直接按这一机制保存。可序列化对象
| 环境变化 | 可能的影响 | 处理思路 |
|---|---|---|
| 读取解释器不支持协议 | 无法解码 | 按最低读取版本选协议 |
| 类被移到其他模块或改名 | 找不到原来的对象定义 | 保留兼容导入路径,或设计迁移过程 |
| 库升级改变状态格式 | 载入失败或含义发生变化 | 同时记录依赖版本和应用 schema |
| 文件截断、损坏 | 抛出加载异常 | 将缓存视为可重建结果,保留原始数据来源 |
| 保存了外部资源状态 | 文件句柄、连接等不能自动恢复为等价资源 | 存描述信息,在合适阶段重新建立资源 |
支持某个协议,不代表所有依赖对象跨版本都兼容。长期交换的数据最好先明确一份独立于 Python 类实现的数据 schema。
文件边界、错误和持久化能力
跳转到“文件边界、错误和持久化能力”loads 读取第一个完整 pickle 对象后会忽略尾随字节。因此“能加载”不能证明整个文件只包含一条记录,也不能充当文件完整性验证。连续写多个对象可以形成应用约定的记录流,但读方也必须按这个约定逐条读取。
加载异常不限于 UnpicklingError;截断数据可能出现 EOFError,对象或模块缺失可能出现 AttributeError、ImportError 等。应用应区分“缓存可以丢弃后重新计算”和“唯一数据必须保留并诊断”,不要只因捕获了一个异常就认定数据有效。
pickle 本身不提供加密、并发写入控制、原子更新或事务。这里只完成对象到字节的转换;需要可靠文件更新时,应在外层设计写入流程。需要固定字段宽度和明确字节序的设备协议,则使用 struct。