Python中的序列化与反序列化:pickle与json模块详解
字数 1184 2025-12-10 06:32:26
Python中的序列化与反序列化:pickle与json模块详解
一、概述与基本概念
1.1 什么是序列化与反序列化
序列化是指将内存中的数据结构或对象状态转换为可以存储或传输的格式(如字节流、字符串)的过程。反序列化则是相反的过程,将序列化后的数据恢复为原始的数据结构或对象。
举个生活化的例子:
- 序列化就像将一本书扫描成PDF文件(便于传输和存储)
- 反序列化就像将PDF文件打印成实体书(恢复原始形态)
1.2 为什么要使用序列化
# 应用场景示例
1. 数据持久化:将数据保存到文件或数据库
2. 网络传输:通过网络发送复杂数据
3. 进程间通信:不同进程间传递数据
4. 缓存存储:将计算结果序列化存储
二、json模块详解
2.1 JSON的基本特点
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,特点如下:
- 文本格式,人类可读
- 语言无关,支持多种编程语言
- 支持的数据类型有限:字符串、数字、布尔、null、数组、对象
2.2 基本使用
2.2.1 序列化操作
import json
# 基本数据类型序列化
data = {
"name": "张三",
"age": 25,
"is_student": True,
"courses": ["Python", "数据结构", "算法"],
"score": None
}
# 将Python对象转换为JSON字符串
json_str = json.dumps(data, indent=2, ensure_ascii=False)
print(json_str)
# 输出:
# {
# "name": "张三",
# "age": 25,
# "is_student": true,
# "courses": ["Python", "数据结构", "算法"],
# "score": null
# }
# 直接写入文件
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
2.2.2 反序列化操作
# 从JSON字符串恢复Python对象
json_data = '{"name": "李四", "age": 30, "married": false}'
python_obj = json.loads(json_data)
print(python_obj) # {'name': '李四', 'age': 30, 'married': False}
print(type(python_obj)) # <class 'dict'>
# 从文件读取
with open("data.json", "r", encoding="utf-8") as f:
data_from_file = json.load(f)
2.3 高级特性
2.3.1 自定义序列化
import json
from datetime import datetime
from decimal import Decimal
# 自定义类
class Person:
def __init__(self, name, birth_date):
self.name = name
self.birth_date = birth_date
# 自定义编码器
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
elif isinstance(obj, Decimal):
return float(obj)
elif isinstance(obj, Person):
return {
"name": obj.name,
"birth_date": obj.birth_date.strftime("%Y-%m-%d")
}
# 调用父类方法处理其他类型
return super().default(obj)
# 使用自定义编码器
person = Person("王五", datetime(1990, 5, 15))
data = {
"person": person,
"timestamp": datetime.now(),
"price": Decimal("99.99")
}
json_str = json.dumps(data, cls=CustomEncoder, ensure_ascii=False)
print(json_str)
2.3.2 自定义反序列化
# 自定义解码器
def object_hook(obj_dict):
if "name" in obj_dict and "birth_date" in obj_dict:
try:
birth_date = datetime.strptime(obj_dict["birth_date"], "%Y-%m-%d")
return Person(obj_dict["name"], birth_date)
except ValueError:
pass
return obj_dict
# 使用object_hook
json_data = '{"person": {"name": "王五", "birth_date": "1990-05-15"}}'
result = json.loads(json_data, object_hook=object_hook)
print(result["person"].name) # 王五
三、pickle模块详解
3.1 pickle的特点
- Python特有的二进制序列化格式
- 可以序列化几乎所有的Python对象
- 序列化后的数据不可读,也不安全
- 通常用于Python程序间的数据交换
3.2 基本使用
3.2.1 序列化操作
import pickle
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
return f"Hello, I'm {self.name}"
# 创建对象
user = User("Alice", 25)
user.friends = ["Bob", "Charlie"] # 动态添加属性
# 序列化为字节流
pickled_data = pickle.dumps(user)
print(type(pickled_data)) # <class 'bytes'>
print(pickled_data[:50]) # 显示部分二进制数据
# 保存到文件
with open("user.pkl", "wb") as f:
pickle.dump(user, f)
3.2.2 反序列化操作
# 从字节流恢复
restored_user = pickle.loads(pickled_data)
print(restored_user.name) # Alice
print(restored_user.greet()) # Hello, I'm Alice
print(restored_user.friends) # ['Bob', 'Charlie']
# 从文件恢复
with open("user.pkl", "rb") as f:
user_from_file = pickle.load(f)
3.3 序列化协议版本
# pickle支持多个协议版本
print("可用协议版本:", pickle.HIGHEST_PROTOCOL) # 通常是5
# 使用指定协议
data = {"a": 1, "b": 2}
# 协议0:ASCII协议,可读性较好但体积大
pickle.dumps(data, protocol=0) # b'(dp0\nVa\nI1\nsVb\nI2\ns.'
# 协议5(Python 3.8+):支持带外数据,性能更好
pickle.dumps(data, protocol=5) # 二进制数据,更紧凑
四、pickle与json的对比
4.1 功能对比
| 特性 | json | pickle |
|---|---|---|
| 数据格式 | 文本(字符串) | 二进制(字节) |
| 可读性 | 人类可读 | 不可读 |
| 安全性 | 相对安全 | 不安全(可执行任意代码) |
| Python对象支持 | 有限(基本类型+dict/list) | 几乎所有Python对象 |
| 跨语言 | 支持 | 不支持(Python专用) |
| 性能 | 较好 | 非常好 |
| 文件大小 | 较大 | 较小 |
4.2 安全性考虑
# pickle的安全风险演示(不要在生产环境运行)
import pickle
import os
class MaliciousClass:
def __reduce__(self):
# 反序列化时会执行系统命令
return (os.system, ('echo "危险操作"',))
# 恶意数据
malicious_data = pickle.dumps(MaliciousClass())
# 反序列化时会执行命令
try:
pickle.loads(malicious_data) # 会输出:危险操作
except:
pass
# 安全建议:只反序列化可信来源的数据
# 或者使用json替代
五、实际应用场景
5.1 缓存实现
import pickle
import hashlib
import os
from functools import wraps
def cache_to_file(prefix="cache"):
"""将函数结果缓存到文件的装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 根据参数生成缓存文件名
key = f"{func.__name__}_{args}_{kwargs}"
filename = f"{prefix}_{hashlib.md5(key.encode()).hexdigest()}.pkl"
# 如果缓存存在且未过期
if os.path.exists(filename):
with open(filename, "rb") as f:
result, timestamp = pickle.load(f)
# 假设缓存有效期1小时
if time.time() - timestamp < 3600:
return result
# 执行函数并缓存结果
result = func(*args, **kwargs)
with open(filename, "wb") as f:
pickle.dump((result, time.time()), f)
return result
return wrapper
return decorator
# 使用示例
@cache_to_file()
def expensive_computation(x):
print(f"计算 {x}...")
time.sleep(2)
return x * x
5.2 配置管理
import json
import yaml # 需要安装PyYAML
class ConfigManager:
def __init__(self, config_file):
self.config_file = config_file
self.config = self._load_config()
def _load_config(self):
"""根据文件后缀自动选择加载方式"""
if self.config_file.endswith('.json'):
with open(self.config_file, 'r') as f:
return json.load(f)
elif self.config_file.endswith('.yaml') or self.config_file.endswith('.yml'):
with open(self.config_file, 'r') as f:
return yaml.safe_load(f)
elif self.config_file.endswith('.pkl'):
with open(self.config_file, 'rb') as f:
return pickle.load(f)
else:
raise ValueError("不支持的配置文件格式")
def save_config(self, filepath=None):
"""保存配置"""
filepath = filepath or self.config_file
if filepath.endswith('.json'):
with open(filepath, 'w') as f:
json.dump(self.config, f, indent=2)
elif filepath.endswith('.pkl'):
with open(filepath, 'wb') as f:
pickle.dump(self.config, f)
六、最佳实践与注意事项
6.1 选择建议
# 根据场景选择合适的序列化方式:
# 1. 需要跨语言交换数据 -> 使用json
# 2. 存储Python对象状态 -> 使用pickle
# 3. 配置文件 -> 使用json或yaml
# 4. 网络传输 -> 考虑msgpack、protobuf等更高效的格式
# 5. 需要版本兼容性 -> 避免使用pickle,因为它与Python版本强相关
6.2 性能优化
import json
import pickle
import timeit
# 大数据量性能测试
large_data = [{"id": i, "value": f"item_{i}"} for i in range(10000)]
# JSON性能
json_time = timeit.timeit(
lambda: json.dumps(large_data),
number=100
)
# Pickle性能(二进制协议)
pickle_time = timeit.timeit(
lambda: pickle.dumps(large_data, protocol=5),
number=100
)
print(f"JSON序列化时间: {json_time:.3f}s")
print(f"Pickle序列化时间: {pickle_time:.3f}s")
6.3 常见问题与解决方案
问题1:循环引用
# pickle可以处理循环引用,json不可以
import json
import pickle
class Node:
def __init__(self, value):
self.value = value
self.next = None
# 创建循环引用
node1 = Node(1)
node2 = Node(2)
node1.next = node2
node2.next = node1 # 循环引用
# json无法序列化
try:
json.dumps(node1)
except TypeError as e:
print(f"JSON错误: {e}") # Object of type Node is not JSON serializable
# pickle可以处理
pickle_data = pickle.dumps(node1)
node1_restored = pickle.loads(pickle_data)
print(node1_restored.next.next.value) # 1
问题2:自定义类的序列化
# 为自定义类添加序列化支持
class SerializableUser:
def __init__(self, name, email):
self.name = name
self.email = email
# 定义序列化时保存的数据
def __getstate__(self):
state = self.__dict__.copy()
# 不保存敏感信息
if 'password' in state:
del state['password']
return state
# 定义反序列化时的行为
def __setstate__(self, state):
self.__dict__.update(state)
# 恢复默认值
self.created_at = "unknown"
# JSON序列化支持
def to_json(self):
return {
"name": self.name,
"email": self.email
}
@classmethod
def from_json(cls, json_data):
return cls(json_data["name"], json_data["email"])
七、高级话题:自定义序列化协议
7.1 实现高效的二进制序列化
import struct
from typing import Any
class BinarySerializer:
"""自定义二进制序列化器"""
@staticmethod
def serialize_int(value: int) -> bytes:
return struct.pack('i', value)
@staticmethod
def serialize_string(value: str) -> bytes:
encoded = value.encode('utf-8')
length = len(encoded)
return struct.pack(f'i{length}s', length, encoded)
@staticmethod
def serialize_dict(data: dict) -> bytes:
result = BinarySerializer.serialize_int(len(data))
for key, value in data.items():
result += BinarySerializer.serialize_string(str(key))
result += BinarySerializer.serialize_string(str(value))
return result
@staticmethod
def deserialize_int(data: bytes, offset: int) -> tuple[int, int]:
value = struct.unpack_from('i', data, offset)[0]
return value, offset + 4
7.2 使用第三方库
# msgpack:高效的二进制序列化,支持多种语言
import msgpack
data = {"name": "Alice", "age": 25, "scores": [95, 88, 92]}
packed = msgpack.packb(data) # 二进制数据,比pickle更紧凑
unpacked = msgpack.unpackb(packed)
# protobuf:Google的高效序列化协议
# 需要定义.proto文件,生成Python代码
# 适合需要严格schema和跨语言支持的场景
总结
序列化和反序列化是Python中重要的数据处理技术:
- json适合需要人类可读、跨语言、相对安全的场景
- pickle适合Python内部使用、需要序列化复杂对象的场景
- 选择时需考虑安全性、性能、兼容性等因素
- 对于生产环境,建议优先使用json,只在必要时使用pickle,并确保数据来源可信
掌握这些知识,你将能够根据不同的应用场景选择合适的序列化方案,并能够处理序列化过程中的各种边界情况。