Skip to content

Python 序列化:给内存数据拍"照片"

引言:从"活鱼"到"鱼干"

想象你钓到一条活鱼:

  • 活鱼(内存中的变量):活蹦乱跳,但离开水(程序结束)就死了,下次想吃要重新钓;
  • 鱼干(序列化后的数据):腌制晒干,可以保存、运输、送给朋友——下次打开还是那条鱼的味道。

序列化就是把内存中的对象变成可以存储或传输的格式——Python 里叫 pickling,其他语言叫 serialization,都是一个意思。


一、为什么需要序列化?

python
d = dict(name='Bob', age=20, score=88)
  • 程序运行时:d 在内存里,随时改、随时用;
  • 程序结束后:内存被回收,d 消失,下次运行又变回初始值。

序列化的作用:把 d 拍成"照片"(bytes/str),存到磁盘或发到网络——下次打开"照片",鱼还是那条鱼


二、Pickle:Python 专用的"相机"

2.1 序列化:内存 → bytes

python
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 → 内存

python
# 方法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.56intfloat
true/falseTrue/False
nullNone

3.3 基本用法

python
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 写入文件

python
# 序列化到文件
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 实例不能直接序列化

python
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 serializable

4.2 方案一:default 参数指定转换函数

python
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 方案二:偷懒写法(推荐)

python
print(json.dumps(s, default=lambda obj: obj.__dict__))

原理obj.__dict__ 是实例的属性字典——任何 class 实例都能一键转 dict

例外:定义了 __slots__ 的类没有 __dict__

4.4 反序列化回对象:object_hook

python
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 存重要数据

python
# ❌ 重要用户数据用 pickle 存,换语言或升级 Python 就废了
pickle.dump(user_data, f)

修正:重要数据、跨语言用 JSON;临时缓存用 pickle。

6.2 误区二:json.dumps 中文乱码

python
obj = dict(name='小明', age=20)
print(json.dumps(obj))   # {"name": "\u5c0f\u660e", ...}

修正ensure_ascii=False 保留中文:

python
print(json.dumps(obj, ensure_ascii=False))   # {"name": "小明", ...}

6.3 误区三:以为 JSON 支持所有 Python 类型

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 误区四:反序列化不可信数据

python
# ❌ 危险!恶意 pickle 数据可能执行任意代码
data = pickle.loads(untrusted_bytes)

修正永远不要 unpickle 不可信来源的数据;JSON 相对安全,但也要验证。


七、实际应用案例

案例 1:游戏存档系统

python
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 数据交换

python
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 参数

python
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)
参考答案
python
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 字符转义为 \uXXXXFalse 保留原字符。


九、小结

  1. 序列化:内存对象 → 可存储/传输格式;反序列化:反过来;
  2. Pickle:Python 专用、二进制、快,但不跨语言、版本可能不兼容、有安全风险
  3. JSON:跨语言、纯文本、Web 标准,但只支持基本类型
  4. 自定义对象default 转 dict 序列化,object_hook 转对象反序列化;
  5. 中文处理ensure_ascii=False 保留中文;
  6. 选择原则Python 内部缓存用 pickle,跨语言/网络传输用 JSON
  7. 安全红线绝不 unpickle 不可信数据

序列化是数据的"时空穿越术"——让内存中的对象,可以穿越时空,在另一台机器、另一个时间复活