Python 序列化 pickle 模块

简介: Python 序列化 pickle 模块

1. 序列化与 pickle 简介

1.1 什么是序列化?

所有的编程一定是围绕数据展开的,而数据呈现形式往往是结构化的,比如结构体(Struct)、类(Class)。 但是当我们通过网络、磁盘等传输、存储数据的时候却要求是二进制流。 比如 TCP 连接,它提供给上层应用的是面向连接的可靠字节流服务。那么如何将这些结构体和类转化为可存储和可传输的字节流呢?这就是序列化要干的事情,反之,从字节流如何恢复为结构化的数据就是反序列化。

序列化解决了对象持久化和跨网络数据交换的问题。要达到这个目的,有几种方法,每一种方法都有其优缺点。

例如,可以将对象数据存储在某种格式的文本文件中,譬如 CSV 文件。或者可以用关系数据库,譬如 Gadfly、MySQL、PostgreSQL 或者 DB2。这些文件格式和数据库都非常优秀,对于所有这些存储机制,Python 都有健壮的接口。

这些存储机制都有一个共同点:存储的数据是独立于对这些数据进行操作的对象和程序。这样做的好处是,数据可以作为共享的资源,供其它应用程序使用。缺点是,用这种方式,可以允许其它程序访问对象的数据,这违背了面向对象的封装性原则 — 即对象的数据只能通过这个对象自身的公共(public)接口来访问。

另外,对于某些应用程序,关系型数据库方法可能不是很理想。尤其是,关系型数据库不理解对象。相反,关系数据库会强行使用自己的类型系统和关系数据模型(表),每张表包含一组元组(行),每行包含具有固定数目的静态类型字段(列)。如果应用程序的对象模型不能够方便地转换到关系模型,那么在将对象映射到元组以及将元组映射回对象方面,会碰到一定难度。这种困难常被称为阻碍性不匹配(impedence-mismatch)问题。

1.2 对象序列化

在 Python 中,序列化过程称为 pickle,可以将对象 pickle 成字符串、磁盘上的文件或者任何类似于文件的对象,也可以将这些字符串、文件或任何类似于文件的对象 unpickle 成原来的对象。

假定您喜欢将任何事物都保存成对象,而且希望避免将对象转换成某种基于非对象存储的开销;那么 pickle 文件可以提供这些好处,但有时可能需要比这种简单的 pickle 文件更健壮以及更具有可伸缩性的事物。例如,只用 pickle 不能解决命名和查找 pickle 文件这样的问题,另外,它也不能支持并发地访问持久性对象。如果需要这些方面的功能,则要求助类似于 ZODB(针对 Python 的 Z 对象数据库)这类数据库。ZODB 是一个健壮的、多用户的和面向对象的数据库系统,它能够存储和管理任意复杂的 Python 对象,并支持事务操作和并发控制。令人足够感兴趣的是,甚至 ZODB 也依靠 Python 的本机序列化能力。而要想有效地使用 ZODB,首先必须充分了解 pickle。

1.3 pickle 模块简介

pickle 提供了一个简单的持久化功能。可以将对象以文件的形式存放在磁盘上。

Python 中几乎所有的数据类型(列表、字典、集合、类等)都可以用 pickle 来序列化。pickle 序列化后的数据,可读性差,人一般无法识别。
pickle 的可移植性

从空间和时间上说,Pickle 是可移植的。例如,可以在 Linux 下创建一个 pickle,然后将它发送到在 Windows 或 Mac OS 下运行的 Python 程序。并且,当升级到更新版本的 Python 时,不必担心可能要废弃已有的 pickle。Python 开发人员已经保证 pickle 格式将可以向后兼容 Python 各个版本。事实上,在 pickle 模块中提供了有关目前以及所支持的格式方面的详细信息。

2. pickle 函数

pickle 模块提供了以下函数:

  • dumps(object):将 python 对象转换(序列化)为字节(二进制)对象。
  • loads(string):将二进制对象转换(反序列为)为 python 对象。
  • dump(object, file):将对象写到文件,这个文件可以是实际的物理文件,也可以是任何类似于文件的对象,这个对象具有 write() 方法,可以接受单个的字符串参数。
  • load(file):返回包含在 pickle 文件中的对象。

缺省情况下, dumps() 和 dump() 使用可打印的 ASCII 表示来创建 pickle。两者都有一个 final 参数(可选,如果为 True,则该参数指定用更快以及更小的二进制表示来创建 pickle)。loads() 和 load() 函数则会自动检测 pickle 是二进制格式还是文本格式。事实上,在 pickle 模块中记录了所有使用的约定。

2.1 dumps() 和 loads()

  • dumps(object):将 python 对象转换(序列化)为字节(二进制)对象。
  • loads(string):将二进制对象转换(反序列为)为 python 对象。

示例:

# 将对象序列化为二进制
>>> o1 = ("this is string", 42, [1,2,3], {1:2}, None)
>>> p1 = pickle.dumps(o1)
>>> print(p1)
b'\x80\x03(X\x0e\x00\x00\x00this is stringq\x00K*]q\x01(K\x01K\x02K\x03e}q\x02K\
x01K\x02sNtq\x03.'

# 将二进制反序列化为对象
>>> o2 = pickle.loads(p1)
>>> print(o2)
('this is string', 42, [1, 2, 3], {1: 2}, None)

在以上示例中,使用的都是简单对象,因此使用二进制 pickle 格式不会在节省空间上显示出太大的效率。然而,在实际使用复杂对象的系统中,使用二进制格式可以在大小和速度方面带来显著的改进。

2.2 dump() 和 load()

  • dump(object, file):将 python 对象写(序列化)到文件,这个文件可以是实际的物理文件,也可以是任何类似于文件的对象,这个对象具有 write() 方法,可以接受单个的字符串参数。
  • load(file):从文件中将二进制对象读取(反序列化)为 python 对象。

dump() 和 load() ,它们使用文件和类似文件的对象。这些函数的操作非常类似于我们刚才所看到的 dumps() 和 loads() ,区别在于它们还有另一种能力 — dump() 函数能一个接着一个地将几个对象转储到同一个文件。随后调用 load() 来以同样的顺序检索这些对象。

示例:

# 将对象序列化到二进制文件中
>>> a1 = "apple"
>>> b1 = {1:"one", 2:"two"}
>>> c1 = [1,"two"]
>>> f1 = open("tmp.pkl", "wb")
>>> pickle.dump(a1, f1)
>>> pickle.dump(b1, f1)
>>> pickle.dump(c1, f1)
>>> f1.close()

# 按序(先入先出)从二进制文件中反序列为对象
>>> f2 = open("tmp.pkl", "rb")
>>> a2 = pickle.load(f2)
>>> a2
'apple'
>>> b2 = pickle.load(f2)
>>> b2
{1: 'one', 2: 'two'}
>>> c2 = pickle.load(f2)
>>> c2
[1, 'two']
>>> f2.close()

3. pickle 高级 —— 复杂对象

到目前为止,我们讲述了关于 pickle 方面的基本知识。在这一节,将讨论一些高级问题,当你开始 pickle 复杂对象时,会遇到这些问题,其中包括定制类的实例。幸运的是,Python 可以很容易地处理这种情形。

3.1 多个引用,同一对象

在 Python 中,变量是对象的引用。同时,也可以用多个变量引用同一个对象。经证明,Python 在用经过 pickle 的对象维护这种行为方面丝毫没有困难。

示例:对象引用的维护

>>> a = [1,2,3]
>>> b = a
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>>
>>> c = pickle.dumps((a,b))
>>> d, e = pickle.loads(c)
>>> d
[1, 2, 3]
>>> e
[1, 2, 3]
>>> d.append(4)
>>> e
[1, 2, 3, 4]

3.2 循环引用和递归引用

可以将刚才演示过的对象引用支持扩展到 递归引用(一个对象包含对其自身的引用)和 循环引用(两个对象各自包含对对方的引用)。

示例:递归引用

>>> li = [1,2,3]
>>> li.append(li)
>>> li
[1, 2, 3, [...]]
>>> li[3]
[1, 2, 3, [...]]
>>> li[3][3]
[1, 2, 3, [...]]
>>> p = pickle.dumps(li)
>>> li2 = pickle.loads(p)
>>> li2
[1, 2, 3, [...]]
>>> li2[3]
[1, 2, 3, [...]]
>>> li2[3][3]
[1, 2, 3, [...]]

示例:循环引用

>>> a = [1,2]
>>> b = [3,4]
>>> a.append(b)
>>> b.append(a)
>>> a
[1, 2, [3, 4, [...]]]
>>> b
[3, 4, [1, 2, [...]]]
>>> a[2]
[3, 4, [1, 2, [...]]]
>>> a[2] is b
True
>>> f1 = open("tmp.pkl","wb")
>>> pickle.dump((a,b), f1)
>>> f1.close()
>>>
>>> f2 = open("tmp.pkl", "rb")
>>> c,d = pickle.load(f2)
>>> f2.close()
>>> c
[1, 2, [3, 4, [...]]]
>>> d
[3, 4, [1, 2, [...]]]
>>> c[2] is d
True

3.3 分别 pickle vs. 在一个元组中一起 pickle

如果分别 pickle 每个对象,而不是在一个元组中一起 pickle 所有对象,会得到略微不同(但很重要)的结果:在 pickle 情形中,每个对象被恢复到一个与原来对象相等的对象,但不是同一个对象。换句话说,每个 pickle 都是原来对象的一个副本。

# 通过元组一起pickle
>>> a = [1,2]
>>> b = a
>>> f1 = open("tmp.pkl", "wb")
>>> pickle.dump((a,b), f1)
>>> f1.close()
>>>
>>> f2 = open("tmp.pkl", "rb")
>>> c,d = pickle.load(f2)
>>> c is a
False
>>> c is d
True
>>>

# 分别pickle
>>> f3 = open("tmp.pkl", "wb")
>>> f3.close()
>>> a2 = [1,2]
>>> b2 = a2
>>> f3 = open("tmp.pkl", "wb")
>>> pickle.dump(a2, f3)
>>> pickle.dump(b2, f3)
>>> f3.close()
>>>
>>> f4 = open("tmp.pkl", "rb")
>>> c2 = pickle.load(f4)
>>> d2 = pickle.load(f4)
>>> f4.close()
>>>
>>> c2
[1, 2]
>>> d2
[1, 2]
>>> c2 is d2
False

3.4 维护分别 pickle 的对象间的引用

有一个选项确实允许分别 pickle 对象,并维护相互之间的引用,只要这些对象都是 pickle 到同一文件即可。 pickle 模块提供了一个 Pickler (与此相对应是 Unpickler ),它能够跟踪已经被 pickle 的对象。通过使用这个 Pickler ,将会通过引用而不是通过值来 pickle 共享和循环引用。

#Python学习交流群:711312441
>>> f1 = open("tmp.pkl", "wb")
>>> pickler = pickle.Pickler(f1)
>>> a = [1,2]
>>> b = a
>>> pickler.dump(a)
>>> pickler.dump(b)
>>> f1.close()
>>>
>>> f2 = open("tmp.pkl", "rb")
>>> unpickler = pickle.Unpickler(f2)
>>> c = unpickler.load()
>>> d = unpickler.load()
>>> c
[1, 2]
>>> d
[1, 2]
>>> c is d  # 注意与上一个示例的结果不同
True

3.5 不可 pickle 的对象

一些对象类型是不可 pickle 的。例如,Python 不能 pickle 文件对象(或者任何带有对文件对象引用的对象),因为 Python 在 unpickle 时不能保证它可以重建该文件的状态。试图 pickle 文件对象会导致以下错误:

>>> f = open("tmp.pkl", "wb")
>>> pickle.dumps(f)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot serialize '_io.BufferedWriter' object

3.6 类实例

与 pickle 简单对象类型相比,pickle 类实例要多加留意。这主要由于 Python 会 pickle 实例数据(通常是 _dict_ 属性)和类的名称,而不会 pickle 类的代码。当 Python unpickle 类的实例时,它会试图使用在 pickle 该实例时的确切的类名称和模块名称(包括任何包的路径前缀)导入包含该类定义的模块。另外要注意,类定义必须出现在模块的最顶层,这意味着它们不能是嵌套的类(在其它类或函数中定义的类)。

当 unpickle 类的实例时,通常不会再调用它们的 init_() 方法。相反,Python 创建一个通用类实例,并应用已进行过 pickle 的实例属性,同时设置该实例的 _class 属性,使其指向原来的类。

对 Python 2.2 中引入的新型类进行 unpickle 的机制与原来的略有不同。虽然处理的结果实际上与对旧型类处理的结果相同,但 Python 使用 copy_reg 模块的 _reconstructor() 函数来恢复新型类的实例。

如果希望对新型或旧型类的实例修改缺省的 pickle 行为,则可以定义特殊的类的方法 _getstate_() 和 _setstate_() ,在保存和恢复类实例的状态信息期间,Python 会调用这些方法。

相关文章
|
2天前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
|
8天前
|
Java 程序员 开发者
Python的gc模块
Python的gc模块
|
11天前
|
数据采集 Web App开发 JavaScript
python-selenium模块详解!!!
Selenium 是一个强大的自动化测试工具,支持 Python 调用浏览器进行网页抓取。本文介绍了 Selenium 的安装、基本使用、元素定位、高级操作等内容。主要内容包括:发送请求、加载网页、元素定位、处理 Cookie、无头浏览器设置、页面等待、窗口和 iframe 切换等。通过示例代码帮助读者快速掌握 Selenium 的核心功能。
50 5
|
9天前
|
Python
SciPy 教程 之 SciPy 模块列表 16
SciPy教程之SciPy模块列表16 - 单位类型。常量模块包含多种单位,如公制、质量、角度、时间、长度、压强、体积、速度、温度、能量、功率和力学单位。示例代码展示了力学单位的使用,如牛顿、磅力和千克力等。
12 0
|
10天前
|
JavaScript Python
SciPy 教程 之 SciPy 模块列表 15
SciPy 教程之 SciPy 模块列表 15 - 功率单位。常量模块包含多种单位,如公制、质量、时间等。功率单位中,1 瓦特定义为 1 焦耳/秒,表示每秒转换或耗散的能量速率。示例代码展示了如何使用 `constants` 模块获取马力值(745.6998715822701)。
13 0
|
10天前
|
JavaScript Python
SciPy 教程 之 SciPy 模块列表 15
SciPy教程之SciPy模块列表15:单位类型。常量模块包含多种单位,如公制、质量、角度、时间、长度、压强、体积、速度、温度、能量、功率和力学单位。功率单位以瓦特(W)表示,1W=1J/s。示例代码展示了如何使用`constants`模块获取马力(hp)的值,结果为745.6998715822701。
13 0
|
JSON 数据格式 Python
用python实现接口测试(八、实现序列化与反序列化)
前言 在python中,序列化可以理解为:把python的对象编码转换为json格式的字符串,反序列化可以理解为:把json格式字符串解码为python数据对象。
1063 0
|
10天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
3天前
|
存储 人工智能 数据挖掘
从零起步,揭秘Python编程如何带你从新手村迈向高手殿堂
【10月更文挑战第32天】Python,诞生于1991年的高级编程语言,以其简洁明了的语法成为众多程序员的入门首选。从基础的变量类型、控制流到列表、字典等数据结构,再到函数定义与调用及面向对象编程,Python提供了丰富的功能和强大的库支持,适用于Web开发、数据分析、人工智能等多个领域。学习Python不仅是掌握一门语言,更是加入一个充满活力的技术社区,开启探索未知世界的旅程。
12 5
|
3天前
|
人工智能 数据挖掘 开发者
探索Python编程:从基础到进阶
【10月更文挑战第32天】本文旨在通过浅显易懂的语言,带领读者从零开始学习Python编程。我们将一起探索Python的基础语法,了解如何编写简单的程序,并逐步深入到更复杂的编程概念。文章将通过实际的代码示例,帮助读者加深理解,并在结尾处提供练习题以巩固所学知识。无论你是编程新手还是希望提升编程技能的开发者,这篇文章都将为你的学习之旅提供宝贵的指导和启发。

热门文章

最新文章