一.序列化和反序列化
在Python中,序列化(Serialization)和反序列化(Deserialization)是处理对象数据的过程,主要用于对象的存储或网络传输。
序列化(Serialization)
序列化是将Python对象转换成一个字节流(或称为序列化后的表示)的过程,这个字节流可以被写入磁盘文件或通过网络发送。序列化使得对象的状态信息能够被保存,之后可以在需要的时候恢复对象。pickle.dump(obj, file, [,protocol])
函数就是执行序列化的操作,各参数意义如下:
obj
:要被序列化的Python对象。file
:文件对象,表示序列化后的数据将被写入的文件或类文件对象(如BytesIO)。protocol
(可选):指定序列化使用的协议版本,默认值是 pickle.HIGHEST_PROTOCOL,不同的协议影响了生成的字节流的兼容性和效率。
反序列化(Deserialization)
反序列化则是将之前序列化得到的字节流恢复成原来的Python对象的过程。这使得从磁盘文件读取或网络接收的数据能再次成为可操作的对象。对应的,pickle.load(file)
函数用于执行反序列化,参数意义如下:
file
:文件对象,从该文件中读取之前序列化的数据并恢复成Python对象。
例子:
假设我们有一个简单的Python对象,我们可以通过以下步骤进行序列化和反序列化:
序列化示例:
import pickle
data = {'name': 'Alice', 'age': 30}
with open('example.pickle', 'wb') as handle:
pickle.dump(data, handle, protocol=pickle.HIGHEST_PROTOCOL)
反序列化示例:
import pickle
with open('example.pickle', 'rb') as handle:
data_loaded = pickle.load(handle)
print(data_loaded) # 输出: {'name': 'Alice', 'age': 30}
通过序列化和反序列化,我们可以轻松地存储复杂的数据结构或在网络间传递这些数据,这对于长期存储、数据交换或分布式计算等场景非常有用。但需要注意的是,pickle模块不是安全的,不应该用来序列化不可信的数据,因为它可以执行任意代码。
pickle序列化实例
下面是使用 pickle
进行序列化和反序列化的两个简单示例。
示例 1: 序列化与反序列化一个简单列表
序列化过程:
import pickle
# 需要被序列化的简单Python对象 - 一个列表
my_list = [1, 'apple', 3.14]
# 打开一个文件用于写入序列化后的数据
with open('example_pickle_list.pkl', 'wb') as handle:
# 使用pickle的dumps或dump方法进行序列化
# dump直接写入文件句柄,dumps则返回一个bytes对象
pickle.dump(my_list, handle, protocol=pickle.HIGHEST_PROTOCOL)
在此过程中,my_list
这个包含整数、字符串和浮点数的列表被转换成一个字节流,并存储到 example_pickle_list.pkl
文件中。
反序列化过程:
import pickle
# 打开之前存储的文件以读取并反序列化数据
with open('example_pickle_list.pkl', 'rb') as handle:
# 使用pickle的loads或load方法进行反序列化
# load直接从文件句柄读取并还原对象,loads则从bytes对象还原
loaded_list = pickle.load(handle)
print(loaded_list) # 输出: [1, 'apple', 3.14]
通过 pickle.load()
,我们能够从二进制文件中恢复出原始的 Python 列表对象。
示例 2: 序列化与反序列化自定义类的对象
假设有一个简单的自定义类 Person
:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
序列化过程:
import pickle
# 创建一个Person对象
person_instance = Person("Alice", 30)
# 序列化Person对象到文件
with open('example_pickle_person.pkl', 'wb') as person_file:
pickle.dump(person_instance, person_file, protocol=pickle.HIGHEST_PROTOCOL)
反序列化过程:
import pickle
# 反序列化解回Person对象
with open('example_pickle_person.pkl', 'rb') as person_file:
loaded_person = pickle.load(person_file)
print(loaded_person) # 输出: Person(name='Alice', age=30)
在这个例子中,即使 Person
类是一个自定义类,pickle
也能够成功地序列化其实例,并在之后正确地反序列化回原始对象状态。
序列化之后的数据形式
使用 pickle
序列化数据后,原始的 Python 对象会被转换成一个二进制格式的字节流。这个字节流包含了对象的类型信息、属性及其值,以及可能的引用信息,以便能够完全重建原始对象。由于它是二进制格式,直接查看时不会像文本格式那样直观易读。
如果你尝试打印出 pickle.dumps()
方法处理后的结果,你会看到类似下面这样的输出,显示为字节序列:
b'\x80\x04\x95\x16\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06Person\x94\x93\x94)\x81\x94}\x94(\x8c\x04name\x94\x8c\x05Alice\x94\x8c\x03age\x94K\x1eub.'
这段二进制数据中包含了Python解释器如何重建对象的指令。每个字节或字节序列代表了特定的含义,比如对象类型、字符串长度、整数值等。例如,\x80\x04
是 pickle 协议版本的标识,\x95\x16
可能指示全局名称查找,\x8c
引入了一个字符串,后面跟着字符串长度和内容,\x94
表示结束,\x93
开始构建一个新对象,等等。
由于这些信息对人类来说难以直接解读,通常我们不会直接查看或编辑这些序列化后的数据,而是依赖 pickle.load()
或 pickle.loads()
来自动解析这些字节流,恢复成原始的 Python 对象。
二.序列化的实现原理
Python中的pickle
模块实现序列化和反序列化的原理主要是通过对象的递归分析及构造。以下是其工作流程的基本概述:
序列化过程(dump)
-
检查对象类型:当
pickle.dump()
被调用时,pickle
首先检查要序列化的对象类型。Python的基本数据类型(如整数、浮点数、字符串等)有预定义的序列化方式。对于复杂类型(如列表、字典、自定义类的实例等),则需要更复杂的处理。 -
递归处理:对于复杂对象,
pickle
会递归地进入对象的每个元素,对它们进行同样的检查和序列化过程。这意味着如果一个对象包含其他对象,这些内部对象也会被序列化。 -
生成二进制流:对于每种类型,
pickle
使用一种特定的编码方案将其转换为一系列的字节。这个过程包括类型标识符、对象的属性或元素值等信息。例如,一个字典会被编码为它的键值对序列,每个键值对又进一步被序列化。 -
写入文件或字节流:最终的字节流按照指定的协议格式编码后,被写入到文件或其他输出流中。协议决定了如何编码类型信息和数据,不同的协议版本提供了向前或向后的兼容性。
反序列化过程(load)
-
读取二进制流:当使用
pickle.load()
从文件或字节流读取时,首先读取并解析字节流中的类型标识符和数据。 -
重建对象:根据解析出的类型标识符,
pickle
会创建相应的Python对象,并根据字节流中的数据填充对象的属性或内容。对于复杂对象,这一过程也是递归进行的,先创建容器对象,然后在其内部重建子对象。 -
处理引用:在序列化复杂数据结构时,如果对象之间存在循环引用或重复引用,
pickle
会通过引用计数或ID映射来处理这些情况,确保反序列化后的对象结构与原始结构一致。 -
返回对象:一旦所有对象都被正确重建,
pickle.load()
会返回最顶层的对象,即原始数据的完全复制品。
安全性考虑
虽然pickle
功能强大,但由于它能够执行任意的Python代码(特别是在处理自定义类和其他可执行代码时),因此使用非可信来源的数据进行反序列化可能存在安全风险。因此,在处理外部输入的数据时,推荐使用更安全的序列化库,如json
或yaml
,或者确保有适当的安全措施来验证和清理数据。
三.为什么需要用到序列化
序列化在软件开发和系统设计中扮演着重要角色,主要有以下几个原因:
-
数据持久化:当程序运行时,内存中的数据是暂时的,程序关闭后数据就会丢失。序列化允许我们将这些数据转换为可以存储在磁盘上的格式,如文件,从而实现数据的持久保存。这样,即使程序重启或系统关闭,之前的状态和数据仍然可以恢复。
-
跨平台/进程通信:在分布式系统或多个程序之间交换数据时,序列化是必需的。通过将对象转换为通用的格式(如JSON、XML或二进制流),可以在不同语言编写的系统之间轻松共享数据,因为这些格式是跨平台理解的。
-
网络传输:在进行网络通信时,如通过HTTP请求或RPC(远程过程调用),数据需要被序列化成适合网络传输的格式。这不仅包括基本类型,还包括复杂对象结构,确保接收方能够准确无误地重建发送方的意图。
-
缓存和复制:序列化有助于实现数据缓存,比如将计算结果序列化后存储,以便下次直接使用而无需重新计算。同时,在分布式系统中,对象的序列化可以用来辅助数据复制,确保数据的一致性。
-
可移植性和兼容性:序列化使得应用程序状态或配置可以轻松地在不同环境之间迁移,无论是不同版本的同一程序,还是完全不同系统之间的数据交换,都提高了软件的灵活性和可维护性。
-
批处理和任务队列:在需要将任务分解并通过消息队列分发时,任务及其所需的数据常常需要被序列化,以便在队列中存储和传输,然后在另一个进程中反序列化并处理。
综上所述,序列化是现代软件开发中不可或缺的技术,它简化了数据存储、传输和交换的复杂性,增强了系统的灵活性和可靠性。