TA的每日心情 | 奋斗 2021-8-16 14:04 |
---|
签到天数: 1 天 连续签到: 1 天 [LV.1]测试小兵
|
持久性就是指保持对象,甚至在多次执行同一程序之间也保持对象。通过本文,您会对 Python对象的各种持
久性机制(从关系数据库到 Python 的 pickle以及其它机制)有一个总体认识。另外,还会让您更深一步地了
解Python 的对象序列化能力。
什么是持久性?
持久性的基本思想很简单。假定有一个 Python 程序,它可能是一个管理日常待办事项的程序,您希望在多次
执行这个程序之间可以保存应用程序对象(待办事项)。换句话说,您希望将对象存储在磁盘上,便于以后
检索。这就是持久性。要达到这个目的,有几种方法,每一种方法都有其优缺点。
例如,可以将对象数据存储在某种格式的文本文件中,譬如 CSV 文件。或者可以用关系数据库,譬如 Gadfly、
MySQL、PostgreSQL 或者 DB2。这些文件格式和数据库都非常优秀,对于所有这些存储机制,Python 都有
健壮的接口。
这些存储机制都有一个共同点:存储的数据是独立于对这些数据进行操作的对象和程序。这样做的好处是,
数据可以作为共享的资源,供其它应用程序使用。缺点是,用这种方式,可以允许其它程序访问对象的数据,
这违背了面向对象的封装性原则 — 即对象的数据只能通过这个对象自身的公共(public)接口来访问。
另外,对于某些应用程序,关系数据库方法可能不是很理想。尤其是,关系数据库不理解对象。相反,关系
数据库会强行使用自己的类型系统和关系数据模型(表),每张表包含一组元组(行),每行包含具有固定
数目的静态类型字段(列)。如果应用程序的对象模型不能够方便地转换到关系模型,那么在将对象映射到
元组以及将元组映射回对象方面,会碰到一定难度。这种困难常被称为阻碍性不匹配(impedence-mismatch
)问题。
对象持久性
如果希望透明地存储 Python 对象,而不丢失其身份和类型等信息,则需要某种形式的对象序列化:它是一
个将任意复杂的对象转成对象的文本或二进制表示的过程。同样,必须能够将对象经过序列化后的形式恢复
到原有的对象。在 Python 中,这种序列化过程称为 pickle,可以将对象 pickle 成字符串、磁盘上的文件或者
任何类似于文件的对象,也可以将这些字符串、文件或任何类似于文件的对象 unpickle 成原来的对象。我们
将在本文后面详细讨论 pickle。
假定您喜欢将任何事物都保存成对象,而且希望避免将对象转换成某种基于非对象存储的开销;那么 pickle
文件可以提供这些好处,但有时可能需要比这种简单的 pickle 文件更健壮以及更具有可伸缩性的事物。例如,
只用 pickle 不能解决命名和查找 pickle 文件这样的问题,另外,它也不能支持并发地访问持久性对象。如果
需要这些方面的功能,则要求助类似于 ZODB(针对 Python 的 Z 对象数据库)这类数据库。ZODB 是一个
健壮的、多用户的和面向对象的数据库系统,它能够存储和管理任意复杂的 Python 对象,并支持事务操作
和并发控制。(请参阅 参考资料,以下载 ZODB。)令人足够感兴趣的是,甚至 ZODB 也依靠 Python 的本
机序列化能力,而且要有效地使用 ZODB,必须充分了解 pickle。
另一种令人感兴趣的解决持久性问题的方法是 Prevayler,它最初是用 Java 实现的(有关 Prevaylor 方面的
developerWorks 文章,请参阅 参考资料)。最近,一群 Python 程序员将 Prevayler 移植到了 Python 上,
另起名为 PyPerSyst,由 SourceForge 托管(有关至 PyPerSyst 项目的链接,请参阅 参考资料)。Prevayle
r/PyPerSyst 概念也是建立在 Java 和 Python 语言的本机序列化能力之上。PyPerSyst 将整个对象系统保存
在内存中,并通过不时地将系统快照 pickle 到磁盘以及维护一个命令日志(通过此日志可以重新应用最新的
快照)来提供灾难恢复。所以,尽管使用 PyPerSyst 的应用程序受到可用内存的限制,但好处是本机对象系
统可以完全装入到内存中,因而速度极快,而且实现起来要比如 ZODB 这样的数据库简单,ZODB 允许对象
的数目比同时在能内存中所保持的对象要多。
既然我们已经简要讨论了存储持久对象的各种方法,那么现在该详细探讨 pickle 过程了。虽然我们主要感兴
趣的是探索以各种方式来保存 Python 对象,而不必将其转换成某种其它格式,但我们仍然还有一些需要关
注的地方,譬如:如何有效地 pickle 和 unpickle 简单对象以及复杂对象,包括定制类的实例;如何维护对象
的引用,包括循环引用和递归引用;以及如何处理类定义发生的变化,从而使用以前经过 pickle 的实例时不
会发生问题。我们将在随后关于 Python 的 pickle 能力探讨中涉及所有这些问题。
一些经过 pickle 的 Python
pickle 模块及其同类模块 cPickle 向 Python 提供了 pickle 支持。后者是用 C 编码的,它具有更好的性能,对
于大多数应用程序,推荐使用该模块。我们将继续讨论 pickle ,但本文的示例实际是利用了 cPickle 。由于
其中大多数示例要用 Python shell 来显示,所以先展示一下如何导入 cPickle ,并可以作为 pickle 来引用它:
>>> import cPickle as pickle
现在已经导入了该模块,接下来让我们看一下 pickle 接口。 pickle 模块提供了以下函数对: dumps(object)
返回一个字符串,它包含一个 pickle 格式的对象; loads(string) 返回包含在 pickle 字符串中的对象; dump
(object, file) 将对象写到文件,这个文件可以是实际的物理文件,但也可以是任何类似于文件的对象,这个
对象具有 write() 方法,可以接受单个的字符串参数; load(file) 返回包含在 pickle 文件中的对象。
缺省情况下, dumps() 和 dump() 使用可打印的 ASCII 表示来创建 pickle。两者都有一个 final 参数(可选),
如果为 True ,则该参数指定用更快以及更小的二进制表示来创建 pickle。 loads() 和 load() 函数自动检测
pickle 是二进制格式还是文本格式。
清单 1 显示了一个交互式会话,这里使用了刚才所描述的 dumps() 和 loads() 函数:
清单 1. dumps() 和 loads() 的演示
- [python] view plaincopyprint?
- >>> import cPickle as pickle
- >>> t1 = ('this is a string', 42, [1, 2, 3], None)
- >>> t1
- ('this is a string', 42, [1, 2, 3], None)
- >>> p1 = pickle.dumps(t1)
- >>> p1
- "(S'this is a string'/nI42/n(lp1/nI1/naI2/naI3/naNtp2/n."
- >>> print p1
- (S'this is a string'
- I42
- (lp1
- I1
- aI2
- aI3
- aNtp2
- .
- >>> t2 = pickle.loads(p1)
- >>> t2
- ('this is a string', 42, [1, 2, 3], None)
- >>> p2 = pickle.dumps(t1, True)
- >>> p2
- '(U/x10this is a stringK*]q/x01(K/x01K/x02K/x03eNtq/x02.'
- >>> t3 = pickle.loads(p2)
- >>> t3
- ('this is a string', 42, [1, 2, 3], None)
复制代码
注:该文本 pickle 格式很简单,这里就不解释了。事实上,在 pickle 模块中记录了所有使用的约定。我们还
应该指出,在我们的示例中使用的都是简单对象,因此使用二进制 pickle 格式不会在节省空间上显示出太大
的效率。然而,在实际使用复杂对象的系统中,您会看到,使用二进制格式可以在大小和速度方面带来显著
的改进。
接下来,我们看一些示例,这些示例用到了 dump() 和 load() ,它们使用文件和类似文件的对象。这些函数
的操作非常类似于我们刚才所看到的 dumps() 和 loads() ,区别在于它们还有另一种能力 — dump() 函数能
一个接着一个地将几个对象转储到同一个文件。随后调用 load() 来以同样的顺序检索这些对象。清单 2 显示
了这种能力的实际应用:
清单 2. dump() 和 load() 示例
- [python] view plaincopyprint?
- >>> a1 = 'apple'
- >>> b1 = {1: 'One', 2: 'Two', 3: 'Three'}
- >>> c1 = ['fee', 'fie', 'foe', 'fum']
- >>> f1 = file('temp.pkl', 'wb')
- >>> pickle.dump(a1, f1, True)
- >>> pickle.dump(b1, f1, True)
- >>> pickle.dump(c1, f1, True)
- >>> f1.close()
- >>> f2 = file('temp.pkl', 'rb')
- >>> a2 = pickle.load(f2)
- >>> a2
- 'apple'
- >>> b2 = pickle.load(f2)
- >>> b2
- {1: 'One', 2: 'Two', 3: 'Three'}
- >>> c2 = pickle.load(f2)
- >>> c2
- ['fee', 'fie', 'foe', 'fum']
- >>> f2.close()
-
复制代码
Pickle 的威力
到目前为止,我们讲述了关于 pickle 方面的基本知识。在这一节,将讨论一些高级问题,当您开始 pickle 复
杂对象时,会遇到这些问题,其中包括定制类的实例。幸运的是,Python 可以很容易地处理这种情形。
可移植性
从空间和时间上说,Pickle 是可移植的。换句话说,pickle 文件格式独立于机器的体系结构,这意味着,例
如,可以在 Linux 下创建一个 pickle,然后将它发送到在 Windows 或 Mac OS 下运行的 Python 程序。并且,
当升级到更新版本的 Python 时,不必担心可能要废弃已有的 pickle。Python 开发人员已经保证 pickle 格式
将可以向后兼容 Python 各个版本。事实上,在 pickle 模块中提供了有关目前以及所支持的格式方面的详细
信息.
清单 3. 检索所支持的格式
- [python] view plaincopyprint?
- >>> pickle.format_version
- '1.3'
- >>> pickle.compatible_formats
- ['1.0', '1.1', '1.2']
-
复制代码
多个引用,同一对象
在 Python 中,变量是对象的引用。同时,也可以用多个变量引用同一个对象。经证明,Python 在用经过
pickle 的对象维护这种行为方面丝毫没有困难,如清单 4 所示:
清单 4. 对象引用的维护
- [python] view plaincopyprint?
- >>> a = [1, 2, 3]
- >>> b = a
- >>> a
- [1, 2, 3]
- >>> b
- [1, 2, 3]
- >>> a.append(4)
- >>> a
- [1, 2, 3, 4]
- >>> b
- [1, 2, 3, 4]
- >>> c = pickle.dumps((a, b))
- >>> d, e = pickle.loads(c)
- >>> d
- [1, 2, 3, 4]
- >>> e
- [1, 2, 3, 4]
- >>> d.append(5)
- >>> d
- [1, 2, 3, 4, 5]
- >>> e
- [1, 2, 3, 4, 5]
复制代码
循环引用和递归引用
可以将刚才演示过的对象引用支持扩展到 循环引用(两个对象各自包含对对方的引用)和 递归引用(一个对
象包含对其自身的引用)。下面两个清单着重显示这种能力。我们先看一下递归引用:
>清单 5. 递归引用
- [python] view plaincopyprint?
- >>> l = [1, 2, 3]
- >>> l.append(l)
- >>> l
- [1, 2, 3, [...]]
- >>> l[3]
- [1, 2, 3, [...]]
- >>> l[3][3]
- [1, 2, 3, [...]]
- >>> p = pickle.dumps(l)
- >>> l2 = pickle.loads(p)
- >>> l2
- [1, 2, 3, [...]]
- >>> l2[3]
- [1, 2, 3, [...]]
- >>> l2[3][3]
- [1, 2, 3, [...]]
复制代码
现在,看一个循环引用的示例:
- 清单 6. 循环引用
- [python] view plaincopyprint?
- >>> a = [1, 2]
- >>> b = [3, 4]
- >>> a.append(b)
- >>> a
- [1, 2, [3, 4]]
- >>> b.append(a)
- >>> a
- [1, 2, [3, 4, [...]]]
- >>> b
- [3, 4, [1, 2, [...]]]
- >>> a[2]
- [3, 4, [1, 2, [...]]]
- >>> b[2]
- [1, 2, [3, 4, [...]]]
- >>> a[2] is b
- 1
- >>> b[2] is a
- 1
- >>> f = file('temp.pkl', 'w')
- >>> pickle.dump((a, b), f)
- >>> f.close()
- >>> f = file('temp.pkl', 'r')
- >>> c, d = pickle.load(f)
- >>> f.close()
- >>> c
- [1, 2, [3, 4, [...]]]
- >>> d
- [3, 4, [1, 2, [...]]]
- >>> c[2]
- [3, 4, [1, 2, [...]]]
- >>> d[2]
- [1, 2, [3, 4, [...]]]
- >>> c[2] is d
- 1
- >>> d[2] is c
- 1
复制代码
|
|