该用 pickle 时仅限可信环境内部短时传递(如 multiprocessing 参数、本地调试缓存);不该用时包括网络传输、用户输入、跨版本读取或长期存储,因其存在反序列化任意代码风险及协议兼容问题。

什么时候该用 pickle,什么时候不该碰它
pickle 是 Python 原生最顺手的对象序列化工具,但它的适用边界非常明确:只在可信环境内部使用。一旦涉及网络传输、跨版本读取或长期存储,pickle 就可能反咬一口。
常见错误现象:AttributeError: Can't get attribute 'X' on <module></module>——这是典型模块路径不一致导致的反序列化失败;或者用 Python 3.9 保存的对象,在 3.12 里加载时报 ValueError: unsupported pickle protocol。
- 只用于进程间短时传递(如
multiprocessing的参数)、本地调试缓存 - 永远不要加载来自用户输入、文件上传或网络响应的
pickle数据——它会直接执行任意代码 - 协议版本建议显式指定:
pickle.dump(obj, f, protocol=pickle.HIGHEST_PROTOCOL),避免默认用 3 或 4 协议造成低版本兼容问题 - 自定义类必须确保
__reduce__或__getstate__行为稳定,否则升级类定义后旧数据无法恢复
json 能存对象吗?能,但得先“扁平化”
json 本身不支持 Python 对象,它只认 dict、list、str、int、float、bool 和 None。想存对象,就得自己负责“拆解”和“组装”。
使用场景:需要跨语言读取、写入配置、记录日志结构体、前端可直读的数据缓存。
立即学习“Python免费学习笔记(深入)”;
- 别直接
json.dump(obj, f)——会报TypeError: Object of type X is not JSON serializable - 用
default=参数处理未知类型,比如把datetime转成 ISO 字符串:json.dump(obj, f, default=lambda x: x.isoformat() if hasattr(x, 'isoformat') else str(x)) - 反序列化时不能自动还原类型,得手动调用构造函数,例如
MyClass(**data)或用object_hook - 注意浮点精度:
json不保留decimal.Decimal,也不区分int和float,数值全按 double 解析
SQLite + sqlite3 自带的 adapt/convert 机制
当对象结构固定、需要查询能力又不想引入 ORM 时,SQLite 是个被低估的选择。Python 的 sqlite3 模块允许注册类型适配器,让自定义类进出数据库像原生类型一样自然。
性能影响:比纯文件序列化略慢,但支持索引、WHERE 查询、事务,适合中等规模结构化持久化。
- 注册适配器前,必须先启用类型检测:
sqlite3.connect(db_path, detect_types=sqlite3.PARSE_DECLTYPES) - 适配器函数返回值只能是
str、bytes、int、float或None,其他类型会被忽略 - 转换器函数接收的是
bytes或str,需自行解析,比如用json.loads还原嵌套结构 - 注意并发:多个连接同时写入时,SQLite 默认以文件锁阻塞,不是真正的多线程安全
用 dataclasses + typing 定义 schema,再选序列化后端
真正可持续的持久化,往往始于清晰的类型契约。用 @dataclass 描述数据结构,配合 typing 注解,能让后续换序列化方案、加校验、生成文档都变得简单。
容易踩的坑:有人一上来就堆 pydantic,结果发现只是存几个配置项,反而增加依赖和启动开销。
- 轻量级场景优先用
dataclasses.asdict()+json,不引入额外依赖 - 字段含嵌套对象?先递归转成 dict,别指望
asdict自动处理非 dataclass 类型 - 需要字段校验或默认值行为更健壮?再考虑
pydantic.BaseModel,但注意v2版本的model_dump()替代了旧版dict() - 如果未来可能迁移到数据库,
dataclass比裸 dict 更容易映射到 ORM 模型,字段名和类型信息都在那里
复杂点不在选哪个方案,而在对象生命周期里哪一环开始失去控制:是类定义改了但旧数据没迁移?还是序列化格式变了却忘了更新读取逻辑?这些地方没有银弹,只有提前约定好谁负责兼容、谁负责报错、谁来写迁移脚本。










