Python 序列化与反序列化指南

1. 序列化概述

在程序运行过程中,所有变量(比如字典、列表、自定义类实例)都临时存储在内存堆栈里,程序一结束,这些内存就会被操作系统回收释放。

但我们经常有「把数据带出去」的需求:比如保存游戏存档下次接着玩、爬虫爬取到的数据存到本地缓存、前后端/微服务之间通过API交换结构化数据……这时就需要把内存中的对象,转换为可存储(存文件/数据库)、可传输(发网络请求)的格式——这个过程就叫序列化(在Python的二进制专属工具里也叫「pickling」,腌制数据)。

反之,把序列化后的「扁平数据」重新还原成内存中能直接操作的对象,就是反序列化(unpickling,解腌)。

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


2. Python pickle 模块

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

2.1 基本用法

核心只有4个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. 从本地文件反序列化
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 虽然方便,但适用场景非常窄,主要有3个硬伤:

核心限制警告
  1. Python 绝对专属:其他语言(Java、Go、JS)完全读不懂pickle二进制串,只能用于同Python环境的数据交换。
  2. 版本兼容性差:不同Python大版本(比如2.x和3.x)甚至小版本(比如3.8和3.12)生成的pickle文件可能不兼容。
  3. 高危安全漏洞:千万千万不要反序列化不受信任的来源的pickle数据!它本质上是在执行一段Python字节码,恶意构造的pickle文件可能会直接运行任意系统命令(比如删除你的重要文件、窃取隐私)。 :::

3. JSON 序列化

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

3.1 数据类型对应表

JSON 是一个「轻量级」格式,只支持6种基本类型,Python 会自动做以下映射:

JSON 类型Python 类型
{}(对象)dict
[](数组)list/tuple(反序列化后默认是list)
"string"(字符串)str(Python 3下自动转Unicode)
1234.56(数字)int/float
true/false(布尔)True/False
null(空值)None

3.2 基本用法

同样是4个核心API,逻辑和pickle高度一致,但处理的是UTF-8文本/二进制文本串

import json

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

# 1. 序列化为UTF-8文本字符串
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. 从文本字符串反序列化
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 默认会把中文字符转义成 \uXXXX 的Unicode编码,虽然不影响程序解析,但人类读起来很麻烦。可以加 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
        # 模拟一个私有属性(双下划线开头的属性__dict__不会直接显示,但可以手动处理)
        self.__secret = "我的梦想是当程序员"
    
    # 手动生成序列化用的字典,还可以加__class__标记类
    def to_dict(self):
        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)

# 反序列化(全局直接用类的from_dict即可)
loaded_s2 = json.loads(flexible_json, object_hook=FlexibleStudent.from_dict)
print(f"加载后的学生类型:{type(loaded_s2)}")  # 输出: <class '__main__.FlexibleStudent'>

5. 安全与最佳实践

不管用哪种序列化方案,都要注意以下几点:

  1. pickle 绝对不要碰不受信任的数据——这条说三遍都不为过。
  2. JSON 要严格验证输入结构——反序列化后可能会拿到不符合预期的字段或类型,比如用 if "user_id" not in data 或者用 pydantic 库做数据校验。
  3. 一定要处理异常——比如JSON格式错误、文件不存在等:
import json

malformed_json = '{"name": "Bob", age: 20}'  # 错误:JSON的键必须加双引号

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

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

对于GB级以上的大型数据高频网络请求,标准的 json 模块性能可能不够用,可以考虑以下替代方案:

6.1 更快的JSON库

  • orjson:目前Python最快的JSON库,返回 bytes 而不是 str,还支持更多Python类型的自动转换(比如 datetimeUUID),安装方式:pip install orjson
  • ujson:速度也比标准库快很多,兼容性略好于 orjson
# orjson 示例
import orjson

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

# 序列化(返回bytes,直接可以写文件或发网络)
orjson_bytes = orjson.dumps(big_data)

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

6.2 二进制跨语言格式

如果需要更高的压缩率和解析速度,可以放弃文本格式,改用二进制:

  • MessagePack:和JSON结构类似,但更紧凑,速度更快,安装方式:pip install msgpack
  • Protocol Buffers:谷歌官方的严格结构化二进制格式,性能最高,但需要先定义 .proto 数据结构文件

7. 总结

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

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

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