一、pickle 是什么?
pickle是 Python 标准库中的对象序列化工具:
- 序列化(dumping / pickling):把内存中的 Python 对象(如列表、字典、自定义类实例等)转换成字节流(bytes),以便:
- 写入文件
- 通过网络发送
- 存入缓存或数据库的二进制字段等
- 反序列化(loading / unpickling):把字节流重新还原为原先的 Python 对象。
特点:
- 支持大多数内置类型和自定义类实例。
- 是 Python 专用的二进制协议,不同语言之间不通用。
- 默认协议不是可读文本(不是 JSON / YAML,而是二进制格式)。
适用场景:在 Python 环境内部保存/恢复复杂对象(如机器学习模型、缓存的中间结果、复杂数据结构等)。
二、基本接口与常用函数
1. 常用函数
importpickle# 写入/读取文件接口pickle.dump(obj,file,protocol=None)# 序列化到文件对象pickle.load(file)# 从文件对象反序列化# 操作 bytes 接口pickle.dumps(obj,protocol=None)# 序列化为 bytespickle.loads(data)# 从 bytes 反序列化protocol:序列化协议版本,整数,一般用:protocol=pickle.HIGHEST_PROTOCOL(推荐)- 不写时使用默认协议(不同 Python 版本默认值不同)
三、基础用法示例
1. 序列化到文件 / 从文件加载
importpickle data={"name":"Alice","age":25,"scores":[95,88,76],"active":True}# 写入文件(推荐使用二进制模式 'wb')withopen("data.pkl","wb")asf:pickle.dump(data,f,protocol=pickle.HIGHEST_PROTOCOL)# 从文件读取withopen("data.pkl","rb")asf:loaded_data=pickle.load(f)print(loaded_data)说明:
- 文件扩展名通常用
.pkl、.pickle或.p,仅是约定,非强制。 - 必须使用二进制模式:
"wb"/"rb"。
2. 转换为 bytes / 从 bytes 恢复
importpickle obj=[1,2,3,{"a":10}]data_bytes=pickle.dumps(obj,protocol=pickle.HIGHEST_PROTOCOL)# 可以用于网络传输、缓存等restored=pickle.loads(data_bytes)print(restored)四、pickle 支持哪些类型?
大多数常见类型都支持:
- 基本类型:
int、float、bool、str、bytes、None - 容器类型:
list、tuple、dict、set、frozenset - 函数、类、实例(有一些限制,见后文)
datetime、decimal.Decimal、fractions.Fraction等常用内置类型- 许多第三方库对象(如大部分
sklearn模型)——库作者实现了对应的序列化协议
不太适合 / 有限制的对象:
- 打开的文件对象、数据库连接、线程、进程、锁等与系统资源强绑定的对象:
- 常常无法完全恢复原状态(反序列化时环境已不同)
- lambda 匿名函数、内部函数、局部类等:普通
pickle不能可靠处理(要用dill等扩展库)
五、pickle 协议(protocol)版本
protocol是决定如何编码对象为字节流的“格式版本”。
- 常用写法:
pickle.dump(obj,f,protocol=pickle.HIGHEST_PROTOCOL) - 查看当前解释器支持的最高协议:
importpickleprint(pickle.HIGHEST_PROTOCOL) - 越新的协议:
- 通常更高效、更紧凑
- 但在旧版本 Python 上可能无法反序列化
跨 Python 版本使用 Pickle 时:
- 如果你需要在不同 Python 版本间共享
.pkl文件,建议:- 在较低版本的 Python 中查看
HIGHEST_PROTOCOL,然后在较高版本中固定使用该协议版本。 - 或者使用更兼容的格式(如 JSON、msgpack 等)。
- 在较低版本的 Python 中查看
六、自定义类的 pickle 行为
1. 直接序列化自定义类实例
默认情况下,自定义类的实例是可以被 pickle 的:
importpickleclassPerson:def__init__(self,name,age):self.name=name self.age=age p=Person("Alice",25)# 序列化data=pickle.dumps(p)# 反序列化p2=pickle.loads(data)print(p2.name,p2.age)要求:
- 类定义在模块的顶层作用域(不是函数内部)。
- 反序列化时,需要能找到同名模块和类定义,否则会失败:
- 也就是说:
Person类定义在mymodule.py中,那么加载 pickle 时环境里也要有mymodule.Person。
- 也就是说:
2. 控制属性:__getstate__与__setstate__
如果你想:
- 某些属性不参与序列化(如打开的文件句柄)
- 序列化前/后做一些处理
可以在类中实现:
classPerson:def__init__(self,name,age,temp_file=None):self.name=name self.age=age self.temp_file=temp_file# 不打算序列化这个属性def__getstate__(self):# 返回要被序列化的“状态”字典state=self.__dict__.copy()# 删除不希望被序列化的属性if'temp_file'instate:delstate['temp_file']returnstatedef__setstate__(self,state):# 从状态恢复实例self.__dict__.update(state)# 反序列化后,可赋默认值或重新构造资源self.temp_file=None说明:
__getstate__返回任何可被 pickle 序列化的对象(通常是 dict)。__setstate__(state)用来根据序列化得到的state恢复对象内部状态。
3. 更底层的控制:__reduce__/__reduce_ex__
当序列化某个对象时,pickle会调用:
obj.__reduce_ex__(protocol)(如存在)- 否则调用
obj.__reduce__()
它们需要返回一种描述如何重新构造该对象的信息,一般形式是一个 2~5 元组,常见形式:
(callable,args,state)示例(简单展示用法,不必死记):
classMyClass:def__init__(self,value):self.value=value self.cache=value*2# 想在恢复时重新计算def__reduce__(self):# 用 MyClass 构造函数和一个参数创建实例reconstruct_callable=self.__class__ reconstruct_args=(self.value,)# state 用于存其他信息,这里暂时用 Nonestate=Nonereturn(reconstruct_callable,reconstruct_args,state)def__setstate__(self,state):# 反序列化后,重新构造 cacheself.cache=self.value*2平时多数情况下只用__getstate__/__setstate__即可,__reduce__适合高级定制或 C 扩展类。
七、处理大型对象与性能优化
1. 使用最高协议 & 二进制 I/O
pickle.dump(obj,f,protocol=pickle.HIGHEST_PROTOCOL)优点:
- 通常速度更快
- 文件更小
2. 使用protocol >= 4的大对象支持
- 新协议对大对象(>4GB)支持更好。
- Python 3.8+ 中的默认协议已足够。
3. 对于多对象序列化
若你想把多个对象“顺序写入”同一个文件:
withopen("multi.pkl","wb")asf:pickle.dump(obj1,f,protocol=pickle.HIGHEST_PROTOCOL)pickle.dump(obj2,f,protocol=pickle.HIGHEST_PROTOCOL)withopen("multi.pkl","rb")asf:a=pickle.load(f)b=pickle.load(f)注意:
- 加载时需要按相同顺序多次调用
pickle.load。 - 不可随机访问中间对象(除非自己管理偏移量)。
如果要实现可随机访问的“对象仓库”,可考虑:
shelve模块(基于 pickle + dbm)- 或自己实现索引(对象位置表 + seek)
八、安全性:务必注意反序列化风险
这是使用 pickle 时最重要的问题。
1. pickle 反序列化是不安全的
官方文档明确说明:
对不可信数据调用pickle.load或pickle.loads是危险的,可能导致代码执行、系统被攻陷。
原因:
pickle协议中可以编码“调用任意可导入对象”的指令。- 恶意构造的 pickle 字节流可以在加载时触发执行任意 Python 代码(RCE)。
总结一句话:
只有在完全信任数据来源的前提下,才能使用
pickle.load/pickle.loads。
2. 安全使用建议
绝对不要对以下来源的数据使用
pickle.loads:- 用户上传的文件 / 表单
- 网络请求中的数据(HTTP body / WebSocket 等)
- 来历不明的
.pkl文件
如果必须支持某种持久化格式给外部使用,建议:
- 使用 JSON、MessagePack、Protocol Buffers、Avro 等更安全和跨语言的序列化格式。
- 或自行设计清晰受限的数据结构(比如只允许 dict/list/str/int 等基本类型)。
在内部(可信环境)用 pickle:
- 仅在团队内部、受控环境下使用
.pkl共享数据。 - 若部署在可能暴露接口的服务中,不要直接接受外部传来的 pickled 数据。
- 仅在团队内部、受控环境下使用
九、实际例子:缓存结果到硬盘
示例:耗时计算结果缓存到本地,下次直接读取。
importosimportpickle CACHE_FILE="result_cache.pkl"defheavy_compute():# 模拟耗时操作fromtimeimportsleep sleep(3)return{"result":42,"detail":[1,2,3]}defget_result():ifos.path.exists(CACHE_FILE):withopen(CACHE_FILE,"rb")asf:returnpickle.load(f)res=heavy_compute()withopen(CACHE_FILE,"wb")asf:pickle.dump(res,f,protocol=pickle.HIGHEST_PROTOCOL)returnresif__name__=="__main__":print("First call:")print(get_result())print("Second call (should be faster):")print(get_result())十、与其它序列化方式的比较(简要)
| 方式 | 格式 | 可读性 | 跨语言 | 支持复杂 Python 对象 | 安全(默认) | 典型用途 |
|---|---|---|---|---|---|---|
| pickle | 二进制 | 否 | 否 | 是 | 否(危险) | Python 内部的对象持久化 |
| JSON | 文本 | 是 | 是 | 限制多(基本类型为主) | 相对安全 | 简单配置、Web 接口、跨语言 |
| msgpack | 二进制 | 否 | 是 | 一般 | 相对安全 | 高效跨语言序列化 |
| protobuf | 二进制 | 否 | 是 | 用 schema 定义结构 | 相对安全 | 大规模服务间通信、强类型约束 |
结论:
- 只在 Python 内部,且数据来源可信:用
pickle很方便,支持自定义类。 - 需要跨语言或不完全可信数据源:优先考虑 JSON / msgpack / protobuf 等。
十一、小结与使用建议
- 基本用法:
pickle.dump(obj, f)/pickle.load(f)处理文件;pickle.dumps(obj)/pickle.loads(bytes)处理内存中的 bytes。
- 对自定义类:
- 顶层定义类,反序列化时保持类可导入;
- 可以用
__getstate__/__setstate__精细控制。
- 性能:
protocol=pickle.HIGHEST_PROTOCOL;- 注意版本兼容问题。
- 安全:
- 永远不要反序列化不可信来源的 pickle 数据;
- 对外部接口应使用更安全、跨语言的格式。
- 场景典型用途:
- Python 项目内部的模型、缓存、中间结果持久化;
- 一次性脚本、实验性数据保存。