• 软件测试技术
  • 软件测试博客
  • 软件测试视频
  • 开源软件测试技术
  • 软件测试论坛
  • 软件测试沙龙
  • 软件测试资料下载
  • 软件测试杂志
  • 软件测试人才招聘
    暂时没有公告

字号: | 推荐给好友 上一篇 | 下一篇

LINUX应用技巧,序列化存储 Python 对象

发布: 2007-7-04 20:06 | 作者: admin | 来源:  网友评论 | 查看: 11次 | 进入软件测试论坛讨论

领测软件测试网

  什么是持久性?
  持久性的基本思想很简单。假定有一个 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 项目的链接,请参阅参考资料)。Prevayler/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() 的演示
  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', 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() 示例
  >>> 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. 检索所支持的格式
  >>> pickle.format_version
  '1.3'
  >>> pickle.compatible_formats
  ['1.0', '1.1', '1.2']
  
  多个引用,同一对象
  在 Python 中,变量是对象的引用。同时,也可以用多个变量引用同一个对象。经证明,Python 在用经过 pickle 的对象维护这种行为方面丝毫没有困难,如清单 4 所示:

清单 4. 对象引用的维护
  >>> 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. 递归引用
  >>> 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. 循环引用
  >>> 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
  
  注意,如果分别 pickle 每个对象,而不是在一个元组中一起 pickle 所有对象,会得到略微不同(但很重要)的结果,如清单 7 所示:
  
  清单 7. 分别 pickle vs. 在一个元组中一起 pickle
  >>> f = file('temp.pkl', 'w')
  >>> pickle.dump(a, f)
  >>> pickle.dump(b, f)
  >>> f.close()
  >>> f = file('temp.pkl', 'r')
  >>> c = pickle.load(f)
  >>> 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
  0
  &

[1] [2] 下一页

延伸阅读

文章来源于领测软件测试网 https://www.ltesting.net/


关于领测软件测试网 | 领测软件测试网合作伙伴 | 广告服务 | 投稿指南 | 联系我们 | 网站地图 | 友情链接
版权所有(C) 2003-2010 TestAge(领测软件测试网)|领测国际科技(北京)有限公司|软件测试工程师培训网 All Rights Reserved
北京市海淀区中关村南大街9号北京理工科技大厦1402室 京ICP备2023014753号-2
技术支持和业务联系:info@testage.com.cn 电话:010-51297073

软件测试 | 领测国际ISTQBISTQB官网TMMiTMMi认证国际软件测试工程师认证领测软件测试网