|
持久性就是指保持对象,甚至在多次执行同一程序之间也保持对象。通过本文,您会对 Python对象的各种持久性机制(从关系数据库到 Python 的 pickle以及其它机制)有一个总体认识。另外,还会让您更深一步地了解Python 的对象序列化能力。
什么是持久性?
持 久性的基本思想很简单。假定有一个 Python 程序,它可能是一个管理日常待办事项的程序,您希望在多次执行这个程序之间可以保存应用程序对象(待办事项)。换句话说,您希望将对象存储在磁盘上,便于 以后检索。这就是持久性。要达到这个目的,有几种方法,每一种方法都有其优缺点。
例如,可以将对象数据存储在某种格式的文本文件中,譬如 CSV 文件。或者可以用关系数据库,譬如 Gadfly、MySQL、PostgreSQL 或者 DB2。这些文件格式和数据库都非常优秀,对于所有这些存储机制,Python 都有健壮的接口。
这 些存储机制都有一个共同点:存储的数据是独立于对这些数据进行操作的对象和程序。这样做的好处是,数据可以作为共享的资源,供其它应用程序使用。缺点 是,用这种方式,可以允许其它程序访问对象的数据,这违背了面向对象的封装性原则 ― 即对象的数据只能通过这个对象自身的公共(public)接口来访问。
另外,对于某些应用程序,关系数据库 方法可能不是很理想。尤其是,关系数据库不理解对象。相反,关系数据库会强行 使用自己的类型系统和关系数据模型(表),每张表包含一组元组(行),每行包含具有固定数目的静态类型字段(列)。如果应用程序的对象模型不能够方便地转 换到关系模型,那么在将对象映射到元组以及将元组映射回对象方面,会碰到一定难度。这种困难常被称为阻碍性不匹配(impedence- mismatch)问题。
一些经过 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() 的演示
复制代码 代码如下:
Welcome To PyCrust 0.7.2 - The Flakiest Python Shell
Sponsored by Orbtech - Your source for Python programming expertise.
Python 2.2.1 (#1,Aug 27 2002,10:22:32)
[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux-i386
Type "copyright","credits" or "license" for more information.
>>> import cPickle as pickle
>>> t1 = ('this is a string',42,[1,2,3],None)
>>> t1
('this is a string',None)
>>> p1 = pickle.dumps(t1)
>>> p1
"(S'this is a string'nI42n(lp1nI1naI2naI3naNtp2n."
>>> print p1
(S'this is a string'
I42
(lp1
I1
aI2
aI3
aNtp2
.
>>> t2 = pickle.loads(p1)
>>> t2
('this is a string',None)
>>> p2 = pickle.dumps(t1,True)
>>> p2
'(Ux10this is a stringK*]qx01(Kx01Kx02Kx03eNtqx02.'
>>> t3 = pickle.loads(p2)
>>> t3
('this is a string',None)
注:该文本 pickle 格式很简单,这里就不解释了。事实上,在 pickle 模块中记录了所有使用的约定。我们还应该指出,在我们的示例中使用的都是简单对象,因此使用二进制 pickle 格式不会在节省空间上显示出太大的效率。然而,在实际使用复杂对象的系统中,您会看到,使用二进制格式可以在大小和速度方面带来显著的改进。
接下来,我们看一些示例,这些示例用到了 dump() 和 load() ,它们使用文件和类似文件的对象。这些函数的操作非常类似于我们刚才所看到的 dumps() 和 loads() ,区别在于它们还有另一种能力 ― dump() 函数能一个接着一个地将几个对象转储到同一个文件。随后调用 load() 来以同样的顺序检索这些对象。清单 2 显示了这种能力的实际应用:
清单 2. dump() 和 load() 示例
复制代码 代码如下:
>>> 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,True)
>>> pickle.dump(c1,True)
>>> f1.close()
>>> f2 = file('temp.pkl','rb')
>>> a2 = pickle.load(f2)
>>> a2
'apple'
>>> b2 = pickle.load(f2)
>>> b2
{1: 'One',3: 'Three'}
>>> c2 = pickle.load(f2)
>>> c2
['fee','fum']
>>> f2.close()
Pickle 的威力
到目前为止,我们讲述了关于 pickle 方面的基本知识。在这一节,将讨论一些高级问题,当您开始 pickle 复杂对象时,会遇到这些问题,其中包括定制类的实例。幸运的是,Python 可以很容易地处理这种情形。
可移植性
从 空间和时间上说,Pickle 是可移植的。换句话说,pickle 文件格式独立于机器的体系结构,这意味着,例如,可以在 Linux 下创建一个 pickle,然后将它发送到在 Windows 或 Mac OS 下运行的 Python 程序。并且,当升级到更新版本的 Python 时,不必担心可能要废弃已有的 pickle。Python 开发人员已经保证 pickle 格式将可以向后兼容 Python 各个版本。事实上,在 pickle 模块中提供了有关目前以及所支持的格式方面的详细信息:
清单 3. 检索所支持的格式
复制代码 代码如下:
>>> pickle.format_version
'1.3'
>>> pickle.compatible_formats
['1.0','1.1','1.2']
多个引用,同一对象
在 Python 中,变量是对象的引用。同时,也可以用多个变量引用同一个对象。经证明,Python 在用经过 pickle 的对象维护这种行为方面丝毫没有困难,如清单 4 所示:
清单 4. 对象引用的维护
复制代码 代码如下:
>>> a = [1,3]
>>> b = a
>>> a
[1,3]
>>> b
[1,3]
>>> a.append(4)
>>> a
[1,3,4]
>>> b
[1,4]
>>> c = pickle.dumps((a,b))
>>> d,e = pickle.loads(c)
>>> d
[1,4]
>>> e
[1,4]
>>> d.append(5)
>>> d
[1,4,5]
>>> e
[1,5]
循环引用和递归引用
可以将刚才演示过的对象引用支持扩展到 循环引用(两个对象各自包含对对方的引用)和 递归引用(一个对象包含对其自身的引用)。下面两个清单着重显示这种能力。我们先看一下递归引用:
>清单 5. 递归引用
复制代码 代码如下:
>>> l = [1,3]
>>> l.append(l)
>>> l
[1,[...]]
>>> l[3]
[1,[...]]
>>> l[3][3]
[1,[...]]
>>> p = pickle.dumps(l)
>>> l2 = pickle.loads(p)
>>> l2
[1,[...]]
>>> l2[3]
[1,[...]]
>>> l2[3][3]
[1,[...]]
现在,看一个循环引用的示例:
清单 6. 循环引用
复制代码 代码如下:
>>> a = [1,2]
>>> b = [3,4]
>>> a.append(b)
>>> a
[1,[3,4]]
>>> b.append(a)
>>> a
[1,[...]]]
>>> b
[3,[...]]]
>>> a[2]
[3,[...]]]
>>> b[2]
[1,[...]]]
>>> a[2] is b
>>> b[2] is a
>>> 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,[...]]]
>>> d
[3,[...]]]
>>> c[2]
[3,[...]]]
>>> d[2]
[1,[...]]]
>>> c[2] is d
>>> d[2] is c
注意,如果分别 pickle 每个对象,而不是在一个元组中一起 pickle 所有对象,会得到略微不同(但很重要)的结果,如清单 7 所示:
清单 7. 分别 pickle vs. 在一个元组中一起 pickle
(编辑:安卓应用网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|