网站首页> 文章专栏> 机器学习之Python(11): Python中pickle/json序列化
机器学习之Python(11): Python中pickle/json序列化
原创 时间:2025-03-20 21:46 作者:管理员 浏览量:209

在 Python 编程的世界里,序列化是一个绕不开的关键话题,它就像数据的 “魔法变身器”,能够将内存中复杂的数据结构和对象,转化为可以存储在磁盘上,或者在网络中传输的格式,比如字节流或者字符串。当我们需要保存程序的运行状态,下次启动时能快速恢复,又或者需要将数据发送给其他程序、其他服务器时,序列化就派上用场了。反序列化则是这个过程的逆操作,把存储或传输的数据重新变回程序中可以直接使用的对象。

Python 标准库中,有两个序列化模块堪称 “明星”,那就是 pickle 和 json。它们各有所长,在不同的场景下发挥着重要作用。pickle 就像是 Python 的 “专属密友”,能够处理几乎所有的 Python 对象,包括自定义的类实例,序列化后的结果是二进制格式,高效但不可读;而 json 则是个 “国际通用翻译官”,它将对象转换为 JSON 字符串这种文本格式,跨语言兼容性超棒,在 Web 开发、不同系统间的数据交互中频繁出镜,不过它只支持基本的数据类型,像字典、列表、字符串、数字、布尔值和 None 。

二、pickle 模块详解

2.1 pickle 模块概述

pickle 是 Python 的内置模块,就像是 Python 程序的 “数据保险箱”,专门负责数据的序列化与反序列化工作。它能把 Python 对象,比如列表、字典、自定义类的实例等,转化成一种特殊的字节流形式,这个过程就叫序列化,也叫 “pickling”。序列化后的字节流可以轻松地存储到文件里,或者在网络中穿梭传输 。而当你需要使用这些数据时,pickle 又能把字节流再变回原来的 Python 对象,这就是反序列化,也叫 “unpickling”。它对 Python 对象的支持相当全面,几乎能处理你能想到的所有 Python 数据类型,不过像一些与 Python 解释器状态紧密绑定的对象,比如打开的文件、套接字连接这些,就没办法用 pickle 处理了。

2.2 核心函数

pickle 模块中,有四个核心函数,它们是实现数据序列化与反序列化的关键 “武器”。
  • dump(obj, file, protocol=None, *, fix_imports=True):这个函数用于将对象obj序列化后写入到文件对象file中。protocol参数是可选的,它指定了序列化使用的协议版本,默认是DEFAULT_PROTOCOL,协议版本越高,序列化后的字节流可能越紧凑、高效,但也可能会影响兼容性。fix_imports参数在特定情况下用于处理 Python 2 和 Python 3 之间的兼容性问题,一般保持默认True即可。示例代码如下:
import pickle
data = {'name': 'Alice', 'age': 25, 'hobbies': ['reading','swimming']}
with open('data.pkl', 'wb') as file:
pickle.dump(data, file)
在这段代码里,我们创建了一个字典data,然后使用pickle.dump()函数将其序列化后写入到名为data.pkl的文件中,这里以二进制写入模式'wb'打开文件。
  • dumps(obj, protocol=None, *, fix_imports=True):与dump函数不同,dumps函数不会将对象写入文件,而是直接把对象obj序列化为字节流并返回。同样,protocol和fix_imports参数的作用与dump函数里的一样。示例如下:
import pickle
data = [1, 2, 3, 4, 5]
serialized_data = pickle.dumps(data)
print(serialized_data)
运行这段代码,你会看到输出的序列化后的字节流,它是一个bytes类型的对象,后续如果需要存储或传输,再进行相应操作。
  • load(file, *, fix_imports=True, encoding="ASCII", errors="strict"):这个函数从文件对象file中读取序列化的数据,并将其反序列化为 Python 对象返回。fix_imports还是用于处理兼容性问题,encoding参数指定了在反序列化时如何解码文本数据,默认是"ASCII",errors参数则控制解码错误的处理方式,默认是"strict",即遇到错误直接抛出异常。比如:
import pickle
with open('data.pkl', 'rb') as file:
loaded_data = pickle.load(file)
print(loaded_data)
这里我们从之前保存的data.pkl文件中读取数据,使用pickle.load()函数将其反序列化,得到原来的 Python 对象并打印输出。
  • loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict"):它接收一个包含序列化数据的字节流对象bytes_object,将其反序列化为 Python 对象返回,其他参数作用与load函数一致。示例:
import pickle
serialized_data = b'\x80\x04\x95\x16\x00\x00\x00\x00\x00\x00\x00]\x94(K\x01K\x02K\x03K\x04K\x05e.'
loaded_data = pickle.loads(serialized_data)
print(loaded_data)
这段代码中,我们直接使用一个序列化后的字节流serialized_data,通过pickle.loads()函数将其还原成原始的 Python 列表对象。

2.3 应用场景与案例

pickle 在很多场景下都能大显身手,为我们的编程工作提供便利。
  • 保存复杂 Python 对象:当我们有自定义的类,并且希望保存其实例的状态时,pickle 就派上用场了。比如一个简单的游戏存档场景,我们定义一个GameSave类来表示游戏存档信息。
import pickle
class GameSave:
def __init__(self, player_name, level, score):
self.player_name = player_name
self.level = level
self.score = score
# 创建游戏存档实例
save = GameSave('Player1', 5, 300)
# 序列化并保存到文件
with open('game_save.pkl', 'wb') as file:
pickle.dump(save, file)
当游戏需要读取存档时,就可以用pickle.load()函数从文件中读取并反序列化:
import pickle
class GameSave:
def __init__(self, player_name, level, score):
self.player_name = player_name
self.level = level
self.score = score
# 从文件中读取并反序列化
with open('game_save.pkl', 'rb') as file:
loaded_save = pickle.load(file)
print(f"玩家: {loaded_save.player_name},等级: {loaded_save.level},分数: {loaded_save.score}")
这样就成功实现了游戏存档的保存与读取,玩家下次打开游戏就能继续之前的进度。
  • 缓存数据:在数据分析或机器学习项目中,有些数据处理过程可能很耗时,我们可以用 pickle 将处理结果缓存起来,下次直接读取缓存数据,节省处理时间。假设我们有一个函数用于计算一些复杂的数据统计信息:
import pickle
import time
def compute_statistics():
# 模拟复杂的数据计算,这里用sleep代替
time.sleep(5)
return {'mean': 10.5,'std': 2.3, 'count': 100}
try:
with open('statistics.pkl', 'rb') as file:
stats = pickle.load(file)
print("从缓存中读取统计信息")
except FileNotFoundError:
stats = compute_statistics()
with open('statistics.pkl', 'wb') as file:
pickle.dump(stats, file)
print("计算并保存统计信息")
print(stats)
第一次运行时,会计算统计信息并保存到文件;之后再次运行,就会直接从文件中读取缓存数据,大大提高了程序的运行效率。

2.4 潜在风险

pickle 虽然功能强大,但在使用时必须警惕它的安全风险。由于 pickle 在反序列化过程中会执行字节流中包含的代码,如果反序列化的数据来自不可信的源,比如从不受信任的网络连接接收的数据,或者读取了一个被恶意篡改的 pickle 文件,就可能导致恶意代码在你的系统中执行,后果不堪设想,比如可能会导致系统文件被删除、敏感信息泄露等严重问题。
曾经就有安全研究人员利用 pickle 的这个特性,构造恶意的 pickle 数据,当目标程序反序列化这些数据时,恶意代码就会获取目标系统的控制权 。所以,千万不要对来源不明的数据进行 pickle 反序列化操作,确保数据来源的可靠性是使用 pickle 的关键前提。在一些需要处理外部数据的场景中,如果一定要用 pickle,建议先对数据进行严格的验证和过滤,或者考虑使用更安全的序列化方式来替代。

三、json 模块详解

3.1 json 模块概述

json,全称为 JavaScript Object Notation,是一种轻量级的数据交换格式,就像不同编程语言之间沟通的 “通用语言”,在 Web 开发、移动应用开发等各种领域都有广泛应用。它以简洁的文本形式来表示结构化数据,易于人阅读和编写,同时也便于机器解析和生成 。Python 的 json 模块是处理 JSON 数据的利器,专门负责在 Python 对象和 JSON 格式字符串之间进行转换,让 Python 程序能轻松与其他系统进行数据交互,比如前后端数据传输时,后端 Python 服务可以把数据用 json 模块序列化为 JSON 格式发送给前端,前端再进行解析处理。

3.2 核心函数

json 模块中同样有四个核心函数,承担着数据转换的重任。
  • dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw):这个函数用于将 Python 对象obj序列化为 JSON 格式,并写入到文件对象fp中。skipkeys参数默认为False,如果字典的键不是基本类型(如字符串、整数、浮点数、布尔值、None),当skipkeys为True时,这些键会被跳过,为False则会抛出TypeError;ensure_ascii默认为True,它决定非 ASCII 字符的处理方式,为True时,非 ASCII 字符会被转义,为False则会直接输出原字符;indent参数用于美化输出,指定缩进的空格数,若为None,输出是紧凑的,若为正整数,每个级别会缩进相应空格;separators是一个元组(item_separator, key_separator),用于指定元素之间和键值对之间的分隔符,默认是(', ', ': ') 。示例:
import json
data = {'name': '张三', 'age': 20, 'hobbies': ['篮球', '阅读']}
with open('info.json', 'w', encoding='utf-8') as file:
json.dump(data, file, ensure_ascii=False, indent=4)
上述代码将字典data序列化为 JSON 格式,并写入info.json文件,设置ensure_ascii=False保证中文字符正常显示,indent=4让输出格式更美观。
  • dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw):与dump不同,dumps是将 Python 对象obj直接序列化为 JSON 格式的字符串并返回,其他参数作用与dump函数相同。例如:
import json
data = [1, 2, 3, 'a', True]
serialized_data = json.dumps(data)
print(serialized_data)
这段代码把列表data序列化为 JSON 字符串并打印,结果是[1, 2, 3, "a", true] 。
  • load(fp, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):从文件对象fp中读取 JSON 数据,并将其反序列化为 Python 对象返回。object_hook参数是一个可选的函数,它会在解码 JSON 对象时被调用,可以用来对解码后的字典进行自定义处理;parse_float、parse_int、parse_constant可分别指定用于解析 JSON 中浮点数、整数、常量(如NaN、Infinity)的函数;object_pairs_hook是一个可选函数,会在解码 JSON 对象时,将对象的键值对以列表形式传入,方便进行特殊处理 。示例:
import json
with open('info.json', 'r', encoding='utf-8') as file:
loaded_data = json.load(file)
print(loaded_data)
这里从info.json文件中读取数据并反序列化,得到原来的 Python 字典对象。
  • loads(s, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):接收一个包含 JSON 数据的字符串s,将其反序列化为 Python 对象返回,各参数功能与load函数类似。比如:
import json
json_str = '{"name": "李四", "age": 25, "is_student": false}'
loaded_data = json.loads(json_str)
print(loaded_data)
运行这段代码,会把 JSON 字符串json_str反序列化为 Python 字典,输出{'name': '李四', 'age': 25, 'is_student': False} 。
对比 pickle,json 的这四个函数操作的对象基本都是文本格式(JSON 字符串),而 pickle 操作的是二进制数据;json 能跨语言使用,pickle 则局限于 Python 环境 。

3.3 应用场景与案例

json 的应用场景非常广泛,在很多项目中都能看到它的身影。
  • Web 开发数据传输:在前后端分离的 Web 架构中,后端 Python 应用通过 JSON 与前端进行数据交互。比如一个简单的用户信息查询接口,后端代码如下:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/user/<int:user_id>', methods=['GET'])
def get_user(user_id):
user = {'id': user_id, 'name': '王五', 'email': 'wangwu@example.com'}
return jsonify(user)
if __name__ == '__main__':
app.run(debug=True)
这里使用 Flask 框架,jsonify函数本质是基于json.dumps将 Python 字典转换为 JSON 格式的响应,前端接收到 JSON 数据后就能轻松解析并展示用户信息。
  • 配置文件存储:许多 Python 项目会使用 JSON 格式的文件来存储配置信息,方便管理和修改。例如一个简单的数据库配置文件config.json:
{
"host": "127.0.0.1",
"port": 3306,
"username": "root",
"password": "password",
"database": "test_db"
}
在 Python 程序中读取这个配置文件的代码如下:
import json
with open('config.json', 'r') as file:
config = json.load(file)
print(config)
通过json.load读取配置文件,程序就能根据这些配置信息连接数据库,进行后续的数据操作。

3.4 自定义编解码

有时候,默认的 JSON 编码器和解码器无法满足我们的需求,比如要处理自定义类的实例。这时候就可以通过继承json.JSONEncoder和json.JSONDecoder类来自定义 JSON 编码器和解码器。
import json
class Book:
def __init__(self, title, author, publication_year):
self.title = title
self.author = author
self.publication_year = publication_year
# 自定义编码器
class BookEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Book):
return {
'title': obj.title,
'author': obj.author,
'publication_year': obj.publication_year
}
return super().default(obj)
# 自定义解码器
class BookDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
super().__init__(object_hook=self.book_decoder, *args, **kwargs)
def book_decoder(self, dct):
if 'title' in dct and 'author' in dct and 'publication_year' in dct:
return Book(dct['title'], dct['author'], dct['publication_year'])
return dct
book = Book('Python基础教程', '张三', 2022)
# 使用自定义编码器进行编码
json_str = json.dumps(book, cls=BookEncoder)
print(json_str)
# 使用自定义解码器进行解码
loaded_book = json.loads(json_str, cls=BookDecoder)
print(loaded_book.title)
在这段代码里,我们定义了Book类,然后分别创建了BookEncoder和BookDecoder类来自定义编码和解码逻辑,这样就可以顺利地对Book类实例进行 JSON 序列化和反序列化操作了。

四、pickle 与 json 对比分析

4.1 安全性

pickle 存在潜在的安全风险,由于它在反序列化时会执行字节流中的代码,如果反序列化的数据来自不可信源,比如从网络上接收的未经验证的数据,或者读取了被恶意篡改的 pickle 文件,就可能导致恶意代码在系统中执行,造成数据泄露、系统被攻击等严重后果 。
而 json 则相对安全得多,它的格式是公开的、标准化的文本格式,只能表示基本的数据类型,并且易于阅读和验证。在反序列化时,不会执行代码,只是将 JSON 字符串解析为对应的 Python 数据类型,因此不存在执行恶意代码的风险,在处理外部数据交互时,json 是更安全的选择 。

4.2 可读性

pickle 序列化后的数据是二进制字节流,无法直接阅读和理解。例如:
import pickle
data = {'key': 'value'}
serialized = pickle.dumps(data)
print(serialized)
# 输出类似 b'\x80\x04\x95\x13\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x03key\x94\x8c\x05value\x94u.'
这样的字节流对于人类来说,几乎没有可读性。
json 序列化后的数据是文本形式的 JSON 字符串,非常易于人阅读和理解。示例:
import json
data = {'key': 'value'}
serialized = json.dumps(data)
print(serialized)
# 输出 {"key": "value"}
清晰明了,即使是非技术人员也能大概理解其中的数据含义。

4.3 兼容性

pickle 是 Python 特有的序列化方式,生成的字节流只能在 Python 环境中进行反序列化,在不同的 Python 版本之间,可能还会存在兼容性问题,比如高版本 Python 序列化的数据,低版本 Python 可能无法正确反序列化 。
json 则是一种跨语言的数据交换格式,它可以在不同的编程语言之间通用。无论是 Python、Java、JavaScript 还是其他支持 JSON 解析的语言,都可以轻松地对 JSON 数据进行序列化和反序列化操作,这使得 json 在不同系统间的数据交互中具有极大的优势,比如在 Web 开发中,前后端可以通过 JSON 进行数据传输。

4.4 可序列化类型

pickle 的能力十分强大,几乎可以序列化所有的 Python 对象,包括自定义的类实例、函数、模块等。例如:
import pickle
def my_function():
print("这是一个函数")
class MyClass:
def __init__(self):
self.value = 42
obj = MyClass()
serialized_function = pickle.dumps(my_function)
serialized_obj = pickle.dumps(obj)
这里函数my_function和类实例obj都能被 pickle 序列化。
json 的局限性则比较明显,它只能序列化 Python 中的基本数据类型,如字典、列表、字符串、数字、布尔值和None 。如果尝试序列化一个自定义类实例或函数,会抛出TypeError异常。例如:
import json
class MyClass:
def __init__(self):
self.value = 42
obj = MyClass()
try:
json.dumps(obj)
except TypeError as e:
print(f"错误: {e}")
# 输出 错误: Object of type MyClass is not JSON serializable

4.5 性能比较

在性能方面,pickle 通常在序列化和反序列化速度上比 json 快,因为它是二进制格式,处理过程相对简单,生成的数据大小也相对较小,尤其是对于复杂的 Python 对象。
json 由于是文本格式,在序列化时需要将数据转换为字符串,反序列化时又要从字符串解析数据,这个过程相对复杂,所以速度较慢,生成的数据大小也会比 pickle 大。例如,对于一个包含大量数据的列表进行序列化:
import json
import pickle
import timeit
data = list(range(10000))
pickle_time = timeit.timeit(lambda: pickle.dumps(data), number=1000)
json_time = timeit.timeit(lambda: json.dumps(data), number=1000)
print(f"pickle序列化1000次耗时: {pickle_time} 秒")
print(f"json序列化1000次耗时: {json_time} 秒")
运行结果通常会显示 pickle 的耗时明显少于 json 。但在实际应用中,如果数据量较小,或者对可读性、跨语言兼容性有要求,性能差异可能并不是选择的首要因素。


pickle 和 json 这两个序列化模块,在 Python 编程中都占据着重要地位,但它们各有千秋。pickle 专注于 Python 环境内部,对 Python 对象的支持近乎 “全能”,序列化后的二进制数据紧凑高效,在保存复杂 Python 对象、进行数据缓存等场景中表现出色,不过其安全性欠佳,且仅限 Python 使用 。json 则是跨语言数据交互的 “宠儿”,以文本形式呈现的 JSON 字符串安全、可读,在 Web 开发、配置文件存储等需要不同系统间数据交换的场景中不可或缺,只是它对数据类型的支持有限,性能也相对较弱。
当你在实际项目中抉择时,如果数据仅在 Python 程序内部流转,且涉及复杂对象的保存与读取,pickle 无疑是首选;而一旦需要与其他语言的系统通信,或者对数据可读性、安全性有较高要求,json 便是最佳拍档。
动动小手 !!!
来说两句吧
最新评论