Python 序列化:给内存数据拍"照片"
引言:从"活鱼"到"鱼干"
想象你钓到一条活鱼:
- 活鱼(内存中的变量):活蹦乱跳,但离开水(程序结束)就死了,下次想吃要重新钓;
- 鱼干(序列化后的数据):腌制晒干,可以保存、运输、送给朋友——下次打开还是那条鱼的味道。
序列化就是把内存中的对象变成可以存储或传输的格式——Python 里叫 pickling,其他语言叫 serialization,都是一个意思。
一、为什么需要序列化?
d = dict(name='Bob', age=20, score=88)- 程序运行时:
d在内存里,随时改、随时用; - 程序结束后:内存被回收,
d消失,下次运行又变回初始值。
序列化的作用:把 d 拍成"照片"(bytes/str),存到磁盘或发到网络——下次打开"照片",鱼还是那条鱼。
二、Pickle:Python 专用的"相机"
2.1 序列化:内存 → bytes
import pickle
d = dict(name='Bob', age=20, score=88)
# 方法1:dumps() 转成 bytes
data = pickle.dumps(d)
print(data) # b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq...'
# 方法2:dump() 直接写入文件
with open('dump.txt', 'wb') as f:
pickle.dump(d, f)生活化理解:pickle.dumps() 是"拍照",pickle.dump() 是"拍照并存相册"。
2.2 反序列化:bytes → 内存
# 方法1:loads() 从 bytes 恢复
with open('dump.txt', 'rb') as f:
data = f.read()
d = pickle.loads(data)
# 方法2:load() 直接从文件恢复
with open('dump.txt', 'rb') as f:
d = pickle.load(f)
print(d) # {'name': 'Bob', 'age': 20, 'score': 88}注意:恢复的是新对象,内容相同,但和原来的 d 不是同一个。
2.3 Pickle 的局限
| 局限 | 说明 |
|---|---|
| Python 专用 | 其他语言读不懂 pickle 格式 |
| 版本兼容 | Python 2 和 3 的 pickle 可能不兼容 |
| 安全性 | 不要 unpickle 不可信的数据,可能执行恶意代码 |
适用场景:Python 程序内部存配置、缓存、临时数据——不重要的数据。
三、JSON:跨语言的"通用照片格式"
3.1 为什么选 JSON?
- 标准格式:所有语言都能读;
- 纯文本:可以直接看、直接改;
- Web 友好:浏览器、服务器通吃。
3.2 JSON 与 Python 类型对应
| JSON 类型 | Python 类型 |
|---|---|
{} | dict |
[] | list |
"string" | str |
1234.56 | int 或 float |
true/false | True/False |
null | None |
3.3 基本用法
import json
d = dict(name='Bob', age=20, score=88)
# 序列化:Python → JSON 字符串
json_str = json.dumps(d)
print(json_str) # '{"name": "Bob", "age": 20, "score": 88}'
# 反序列化:JSON 字符串 → Python
d2 = json.loads(json_str)
print(d2) # {'name': 'Bob', 'age': 20, 'score': 88}生活化理解:pickle 是"宝丽来相机"(只有 Python 能洗照片),json 是"JPEG 格式"(任何手机电脑都能看)。
3.4 写入文件
# 序列化到文件
with open('data.json', 'w') as f:
json.dump(d, f)
# 从文件反序列化
with open('data.json', 'r') as f:
d = json.load(f)四、JSON 进阶:序列化自定义对象
4.1 问题:class 实例不能直接序列化
class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
s = Student('Bob', 20, 88)
print(json.dumps(s)) # ❌ TypeError: Student is not JSON serializable4.2 方案一:default 参数指定转换函数
def student2dict(std):
return {
'name': std.name,
'age': std.age,
'score': std.score
}
print(json.dumps(s, default=student2dict))
# {"name": "Bob", "age": 20, "score": 88}原理:dumps() 遇到不认识的对象,调用 default 函数转成 dict,再序列化。
4.3 方案二:偷懒写法(推荐)
print(json.dumps(s, default=lambda obj: obj.__dict__))原理:obj.__dict__ 是实例的属性字典——任何 class 实例都能一键转 dict。
例外:定义了 __slots__ 的类没有 __dict__。
4.4 反序列化回对象:object_hook
def dict2student(d):
return Student(d['name'], d['age'], d['score'])
json_str = '{"name": "Bob", "age": 20, "score": 88}'
s = json.loads(json_str, object_hook=dict2student)
print(s) # <__main__.Student object at 0x...>生活化理解:default 是"拍照时告诉相机怎么拍学生",object_hook 是"洗照片时告诉师傅怎么还原学生"。
五、知识链条:从内存到跨语言传输
内存中的 Python 对象
↓
序列化
├─ Pickle → bytes(Python 专用,快)
└─ JSON → str(跨语言,通用)
↓
存储/传输
├─ 写入文件(dump)
├─ 网络传输(HTTP/API)
└─ 缓存(Redis/Memcached)
↓
反序列化
├─ Pickle.load → Python 对象
└─ json.loads → dict/list → (可选)object_hook → 自定义对象六、常见误区与避坑指南
6.1 误区一:pickle 存重要数据
# ❌ 重要用户数据用 pickle 存,换语言或升级 Python 就废了
pickle.dump(user_data, f)修正:重要数据、跨语言用 JSON;临时缓存用 pickle。
6.2 误区二:json.dumps 中文乱码
obj = dict(name='小明', age=20)
print(json.dumps(obj)) # {"name": "\u5c0f\u660e", ...}修正:ensure_ascii=False 保留中文:
print(json.dumps(obj, ensure_ascii=False)) # {"name": "小明", ...}6.3 误区三:以为 JSON 支持所有 Python 类型
import datetime
d = {'time': datetime.now()}
print(json.dumps(d)) # ❌ TypeError: datetime is not JSON serializable修正:JSON 只支持基本类型(dict/list/str/int/float/bool/None),其他类型先转 str 或自定义 default。
6.4 误区四:反序列化不可信数据
# ❌ 危险!恶意 pickle 数据可能执行任意代码
data = pickle.loads(untrusted_bytes)修正:永远不要 unpickle 不可信来源的数据;JSON 相对安全,但也要验证。
七、实际应用案例
案例 1:游戏存档系统
import json
import os
class GameSave:
def __init__(self, save_path='save.json'):
self.save_path = save_path
def save(self, player_data):
"""保存游戏进度"""
# 添加存档时间
player_data['saved_at'] = datetime.now().isoformat()
with open(self.save_path, 'w', encoding='utf-8') as f:
json.dump(player_data, f, ensure_ascii=False, indent=2)
print(f'游戏已保存到 {self.save_path}')
def load(self):
"""读取游戏进度"""
if not os.path.exists(self.save_path):
print('没有找到存档')
return None
with open(self.save_path, 'r', encoding='utf-8') as f:
data = json.load(f)
print(f'读取存档,保存时间: {data.get("saved_at")}')
return data
# 使用
save_manager = GameSave('my_game_save.json')
# 保存
player = {
'name': '勇者',
'level': 25,
'hp': 100,
'inventory': ['剑', '盾', '药水'],
'position': {'x': 120, 'y': 80}
}
save_manager.save(player)
# 下次游戏时读取
loaded = save_manager.load()
if loaded:
print(f"欢迎回来,{loaded['name']}!等级: {loaded['level']}")生活化理解:存档系统是"游戏记忆卡"——进度拍成 JSON 照片存进去,下次打开照片继续玩。
案例 2:API 数据交换
import json
import requests
class WeatherAPI:
"""天气 API 客户端"""
BASE_URL = 'https://api.example.com/weather'
def get_weather(self, city):
"""获取城市天气"""
# 发送请求(实际用 requests,这里模拟)
# response = requests.get(f'{self.BASE_URL}?city={city}')
# data = response.json()
# 模拟返回 JSON
json_str = '''
{
"city": "北京",
"temperature": 25,
"humidity": 60,
"description": "晴",
"forecast": [
{"day": "周一", "high": 28, "low": 18},
{"day": "周二", "high": 26, "low": 17}
]
}
'''
# 反序列化
data = json.loads(json_str)
return data
def display(self, city):
"""显示天气"""
data = self.get_weather(city)
print(f"城市: {data['city']}")
print(f"温度: {data['temperature']}°C")
print(f"天气: {data['description']}")
print("预报:")
for day in data['forecast']:
print(f" {day['day']}: {day['low']}°C ~ {day['high']}°C")
# 使用
api = WeatherAPI()
api.display('北京')生活化理解:API 是"外卖平台"——你下单(发请求),平台做菜(服务器处理),外卖员送来 JSON 格式的"餐盒"(数据),你打开就吃(解析使用)。
八、实战练习
练习:观察 ensure_ascii 参数
import json
obj = dict(name='小明', age=20)
# 默认
s1 = json.dumps(obj)
print(s1)
# ensure_ascii=False
s2 = json.dumps(obj, ensure_ascii=False)
print(s2)参考答案
import json
obj = dict(name='小明', age=20)
s1 = json.dumps(obj, ensure_ascii=True)
print(s1) # {"name": "\u5c0f\u660e", "age": 20}
s2 = json.dumps(obj, ensure_ascii=False)
print(s2) # {"name": "小明", "age": 20}结论:ensure_ascii=True(默认)把非 ASCII 字符转义为 \uXXXX;False 保留原字符。
九、小结
- 序列化:内存对象 → 可存储/传输格式;反序列化:反过来;
- Pickle:Python 专用、二进制、快,但不跨语言、版本可能不兼容、有安全风险;
- JSON:跨语言、纯文本、Web 标准,但只支持基本类型;
- 自定义对象:
default转 dict 序列化,object_hook转对象反序列化; - 中文处理:
ensure_ascii=False保留中文; - 选择原则:Python 内部缓存用 pickle,跨语言/网络传输用 JSON;
- 安全红线:绝不 unpickle 不可信数据。
序列化是数据的"时空穿越术"——让内存中的对象,可以穿越时空,在另一台机器、另一个时间复活。