Python 序列化与反序列化指南

1. 序列化概述

程序运行时,所有的变量——无论是简单的字典、列表,还是复杂的自定义类实例——都临时存放在内存堆栈中。一旦程序结束,操作系统会立即回收这些内存,数据也就烟消云散了。

但在实际开发中,我们经常需要“留住数据”:

  • 把游戏进度保存成存档,下次接着玩;
  • 将爬虫抓取的数据缓存到本地,避免重复请求;
  • 在前后端、微服务之间通过 API 传递结构化的信息……

这时,就需要把内存里那些“活的、立体的对象”,转换成可以存储(如写入文件、数据库)或可以传输(如通过网络发送)的格式。这个过程就叫序列化
(在 Python 的二进制专属工具里,这个过程也被形象地称为 pickling —— 把数据“腌起来”保存。)

反过来,将这些扁平化的数据还原成内存中可以直接操作的对象,就是反序列化unpickling,解腌)。

接下来,我们就来看看 Python 中最常用的两种序列化方案。


2. Python pickle 模块

pickle 是 Python 自带的最简单、最直接的二进制序列化工具
它几乎能处理所有 Python 内置类型,甚至连带方法的自定义类实例也不在话下。

2.1 基本用法

核心 API 只有四个,非常好记:

  • dumps(obj):将对象序列化为 bytes 类型的二进制串;
  • dump(obj, file):直接序列化并写入文件对象;
  • loads(bytes):从二进制串反序列化回对象;
  • load(file):从文件对象反序列化回对象。
import pickle

# 一个混合了多种类型的字典,pickle 完全可以处理
game_save = {
    "player": "小明",
    "level": 15,
    "inventory": ["铁剑", "生命药水*3", "金币120"],
    "hp": 88.5,
    "is_alive": True
}

# 1. 序列化为内存中的二进制串
serialized_bytes = pickle.dumps(game_save)
print(f"序列化后的二进制串(前 20 字节):{serialized_bytes[:20]}")

# 2. 直接写入本地文件(务必使用二进制写入模式 'wb')
with open("game_save.pkl", "wb") as f:
    pickle.dump(game_save, f)

# 3. 从本地文件反序列化(二进制读取模式 'rb')
with open("game_save.pkl", "rb") as f:
    loaded_save = pickle.load(f)

print(f"加载后的玩家信息:{loaded_save['player']},等级 {loaded_save['level']}")

2.2 pickle 的局限性

pickle 虽然方便,但适用场景非常狭窄,主要有三个硬伤:

核心限制警告
  1. Python 绝对专属
    其他语言(Java、Go、JavaScript 等)完全无法理解 pickle 生成的二进制数据,因此它只能用于 Python 环境内部的数据交换。

  2. 版本兼容性差
    不同 Python 大版本(例如 2.x 和 3.x)甚至小版本(如 3.8 和 3.12)之间生成的 pickle 文件很可能不兼容,升级解释器后可能无法正常读取旧存档。

  3. 高危安全漏洞
    千万不要反序列化来自不受信任来源的 pickle 数据!
    pickle 的还原过程本质上是在执行一段 Python 字节码,恶意构造的 pickle 文件可以直接运行任意系统命令,从而删除你的重要文件、窃取隐私甚至控制你的电脑。 :::


3. JSON 序列化

JSON(JavaScript Object Notation)是目前最通用的跨语言文本序列化格式
不仅 Python、JavaScript、Java、Go 等主流语言都原生支持,而且 JSON 本身是纯文本,人类读起来也很清晰。

3.1 数据类型对应表

JSON 是一种“轻量级”格式,只支持六种基本类型。
Python 的标准库 json 在序列化和反序列化时会自动进行类型映射:

JSON 类型Python 类型
{}(对象)dict
[](数组)list / tuple(反序列化后默认为 list)
"string"(字符串)str
1234.56(数字)intfloat
true / falseTrue / False
nullNone

3.2 基本用法

json 模块的 API 设计和 pickle 非常相似,核心方法还是四个,但处理的对象是 UTF-8 文本文本形式的字符串

import json

# 一个符合 JSON 规则的 Python 字典
api_data = {
    "code": 200,
    "msg": "success",
    "data": {
        "user_id": 10086,
        "username": "Alice",
        "favorites": ["Python", "读书", "旅行"]
    }
}

# 1. 序列化为 JSON 字符串(文本)
json_str = json.dumps(api_data)
print(f"序列化后的 JSON 文本:{json_str}")

# 2. 格式化输出 —— indent 参数指定缩进空格数,可读性更高
pretty_json = json.dumps(api_data, indent=2)
print(f"格式化后的 JSON:\n{pretty_json}")

# 3. 直接写入本地文件(使用文本写入模式 'w',默认 UTF-8)
with open("api_response.json", "w", encoding="utf-8") as f:
    json.dump(api_data, f, indent=2, ensure_ascii=False)  # ensure_ascii 稍后解释

# 4. 从 JSON 字符串反序列化回 Python 对象
loaded_data = json.loads(json_str)
print(f"加载后的用户 ID:{loaded_data['data']['user_id']}")

# 5. 从本地文件反序列化
with open("api_response.json", "r", encoding="utf-8") as f:
    loaded_from_file = json.load(f)

3.3 处理好中文字符(一个实用小技巧)

::: tip 中文显示优化 默认情况下,json.dumps 会把非 ASCII 字符(如中文)转义为 \uXXXX 的形式。
对于程序来说前后端都能正常解析,但人类肉眼阅读时非常不友好。
只需加上 ensure_ascii=False,就能保留原生的中文字符。
同时一定要记得在读写文件时显式指定 encoding="utf-8",避免出现乱码。

chinese_data = {"name": "小红", "address": "北京市朝阳区"}

# 默认行为:中文被转义
print(json.dumps(chinese_data))
# 输出:{"name": "\u5c0f\u7ea2", "address": "\u5317\u4eac\u5e02\u671d\u9633\u533a"}

# 保留中文阅读版
print(json.dumps(chinese_data, ensure_ascii=False))
# 输出:{"name": "小红", "address": "北京市朝阳区"}

4. 序列化自定义对象

json 模块默认不能直接处理自定义类的实例,需要我们自己提供“对象 → 字典”的转换逻辑。
常用的方式有两种。

4.1 简单方法:直接使用 __dict__

如果你的类只是一个纯粹的数据容器,没有私有属性,也没有复杂的继承关系,那么可以直接使用 Python 对象自带的 __dict__ 属性——它会自动把实例的所有公开属性打包成一个字典。

class SimpleStudent:
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score

s1 = SimpleStudent("Bob", 20, 88)

# 序列化:通过 default 参数指定转换函数
simple_json = json.dumps(s1, default=lambda obj: obj.__dict__, ensure_ascii=False)
print(simple_json)  # {"name": "Bob", "age": 20, "score": 88}

# 反序列化:通过 object_hook 参数指定还原函数
def dict_to_simple_student(d):
    return SimpleStudent(d["name"], d["age"], d["score"])

loaded_s1 = json.loads(simple_json, object_hook=dict_to_simple_student)
print(f"加载后的学生:{loaded_s1.name},分数 {loaded_s1.score}")

4.2 更灵活的方法:实现专属 to_dictfrom_dict

当你的类存在私有属性、继承自父类的属性,或者希望在序列化时标记类信息方便全局反序列化,更推荐在类内部专门实现转换方法。

class FlexibleStudent:
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.__secret = "我的梦想是当程序员"  # 私有属性,__dict__ 不会直接暴露
    
    def to_dict(self):
        # 手动构建需要序列化的字典,并可加入 __class__ 标记元信息
        return {
            "__class__": "FlexibleStudent",
            "name": self.name,
            "age": self.age,
            "score": self.score,
            # 如果需要,也可以手动暴露部分私有属性
            # "secret": self.__secret
        }
    
    @classmethod
    def from_dict(cls, d):
        if d.get("__class__") == "FlexibleStudent":
            return cls(d["name"], d["age"], d["score"])
        # 如果不是目标类,直接返回字典本身,避免影响其他数据的还原
        return d

s2 = FlexibleStudent("小红", 19, 92)

# 序列化
flexible_json = json.dumps(s2, default=lambda obj: obj.to_dict(), ensure_ascii=False)
print(flexible_json)

# 反序列化 —— 全局只需要调用这个类方法即可
loaded_s2 = json.loads(flexible_json, object_hook=FlexibleStudent.from_dict)
print(f"加载后的学生类型:{type(loaded_s2)}")  # <class '__main__.FlexibleStudent'>

5. 安全与最佳实践

不论你选择哪一种序列化方案,都请牢牢记住以下几点:

  1. pickle 绝对不要碰不受信任的数据
    这一点再强调也不为过。只在自己完全掌控的脚本、本地缓存或内部管道中使用 pickle。

  2. 对 JSON 输入进行严格的结构校验
    反序列化后的数据很可能会缺失字段或出现不期望的类型,你需要手动检查,或者使用 pydantic 等校验库来保证数据结构的可靠。

  3. 始终做好exception-handling
    无论是文件不存在、权限不足,还是 JSON 格式错误,都可能随时发生。
    示例:

import json

malformed_json = '{"name": "Bob", age: 20}'  # 错误:键 age 缺少双引号

try:
    data = json.loads(malformed_json)
except json.JSONDecodeError as e:
    print(f"JSON 解析失败:错误位置 {e.pos},错误信息:{e.msg}")
except FileNotFoundError:
    print("文件不存在")

6. 性能优化(大数据场景)

当处理的数据量达到 GB 级别,或者在高并发网络请求中频繁使用 JSON 时,标准库 json 可能会成为性能瓶颈。此时不妨考虑以下替代方案。

6.1 更快的 JSON 库

  • orjson
    目前 Python 生态中最快的 JSON 库,安装方式:pip install orjson
    它返回的是 bytes 而不是 str,并且可以自动序列化 datetimeUUID 等常见类型。

  • ujson
    速度也比标准库快得多,兼容性略好于 orjson,安装方式:pip install ujson

import orjson

big_data = [{"id": i, "value": f"data_{i}"} for i in range(100_000)]

# 序列化后得到 bytes,可直接写入文件或通过网络发送
orjson_bytes = orjson.dumps(big_data)

# 反序列化
loaded_big_data = orjson.loads(orjson_bytes)

6.2 二进制跨语言格式

如果对解析速度数据压缩率有更高要求,可以放弃纯文本的 JSON,转而使用二进制格式:

  • MessagePack
    结构与 JSON 类似,但体积更小、速度更快。安装方式:pip install msgpack

  • Protocol Buffers(protobuf)
    谷歌出品的结构化二进制序列化格式,性能最强、压缩率最高,但需要提前编写 .proto 文件来定义数据结构,上手成本稍高。


7. 总结

选择序列化方案时,请根据你的核心需求做出权衡:

方案适用场景优点缺点
picklePython 内部临时存数据、本地缓存支持几乎所有 Python 类型,使用简单不跨语言,不安全,版本兼容差
标准 json前后端 / 跨语言 API,小型数据存储跨语言,安全,人类可读性能一般,只支持基础类型
orjson / ujson高频 JSON 处理,大规模数据存储或传输跨语言,安全,速度极快兼容性略低,orjson 返回 bytes
Protocol Buffers跨语言、严格结构、超高频数据交换性能最高,压缩率最高需定义 .proto,学习成本高

最后再强调一遍:安全第一,pickle 只信自己!