Python pickle模块学习(超级详细)

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

pickle模块只能在python中使用,python中几乎所有的数据类型(列表,字典,集合,类等)都可以用pickle来序列化,

pickle序列化后的数据,可读性差,人一般无法识别。

------------------------------------------

pickle.dump(obj, file[, protocol])
  序列化对象,并将结果数据流写入到文件对象中。参数protocol是序列化模式,默认值为0,表示以文本的形式序列化。protocol的值还可以是1或2,表示以二进制的形式序列化。

------------------------------------------
pickle.load(file)
  反序列化对象。将文件中的数据解析为一个Python对象。

其中要注意的是,在load(file)的时候,要让python能够找到类的定义,否则会报错:

比如下面的例子

[python] view plain copy
  1. import pickle  
  2. class Person:  
  3.     def __init__(self,n,a):  
  4.         self.name=n  
  5.         self.age=a  
  6.     def show(self):  
  7.         print self.name+"_"+str(self.age)  
  8. aa = Person("JGood"2)  
  9. aa.show()  
  10. f=open('d:\\p.txt','w')  
  11. pickle.dump(aa,f,0)  
  12. f.close()  
  13. #del Person  
  14. f=open('d:\\p.txt','r')  
  15. bb=pickle.load(f)  
  16. f.close()  
  17. bb.show()  

如果不注释掉del Person的话,那么会报错如下:

意思就是当前模块找不到类的定义了。

--------------------------------------------------

clear_memo()
  清空pickler的“备忘”。使用Pickler实例在序列化对象的时候,它会“记住”已经被序列化的对象引用,所以对同一对象多次调用dump(obj),pickler不会“傻傻”的去多次序列化。
看下面的例子:

[python] view plain copy
  1. import StringIO  
  2. import pickle  
  3. class Person:  
  4.     def __init__(self,n,a):  
  5.         self.name=n  
  6.         self.age=a  
  7.     def show(self):  
  8.         print self.name+"_"+str(self.age)  
  9. aa = Person("JGood"2)  
  10. aa.show()  
  11. fle = StringIO.StringIO()   
  12. pick = pickle.Pickler(fle)  
  13. pick.dump(aa)  
  14. val1=fle.getvalue()  
  15. print len(val1)  
  16. pick.clear_memo()  
  17. pick.dump(aa)  
  18. val2=fle.getvalue()  
  19. print len(val2)  
  20. fle.close()  

上面的代码运行如下:

如果不注释掉,则运行结果是第二个。如果注释掉,那么运行结果是第一个。

主要是因为,python的pickle如果不clear_memo,则不会多次去序列化对象。

原文链接:点击打开链接


持久性就是指保持对象,甚至在多次执行同一程序之间也保持对象。通过本文,您会对 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 项目的链接,请参阅 参考资料)。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() 的演示
  1. >>> import cPickle as pickle  
  2. >>> t1 = ('this is a string'42, [123], None)  
  • >>> t1  
  • ('this is a string'42, [123], 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, [123], 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, [123], None)  
 
注:该文本 pickle 格式很简单,这里就不解释了。事实上,在 pickle 模块中记录了所有使用的约定。我们还应该指出,在我们的示例中使用的都是简单对象,因此使用二进制 pickle 格式不会在节省空间上显示出太大的效率。然而,在实际使用复杂对象的系统中,您会看到,使用二进制格式可以在大小和速度方面带来显著的改进。
接下来,我们看一些示例,这些示例用到了 dump() 和 load() ,它们使用文件和类似文件的对象。这些函数的操作非常类似于我们刚才所看到的 dumps() 和 loads() ,区别在于它们还有另一种能力 — dump() 函数能一个接着一个地将几个对象转储到同一个文件。随后调用 load() 来以同样的顺序检索这些对象。清单 2 显示了这种能力的实际应用:
清单 2. dump() 和 load() 示例
  1. >>> a1 = 'apple'  
  2. >>> b1 = {1'One'2'Two'3'Three'}  
  3. >>> c1 = ['fee''fie''foe''fum']  
  4. >>> f1 = file('temp.pkl''wb')  
  5. >>> pickle.dump(a1, f1, True)  
  6. >>> pickle.dump(b1, f1, True)  
  7. >>> pickle.dump(c1, f1, True)  
  8. >>> f1.close()  
  9. >>> f2 = file('temp.pkl''rb')  
  10. >>> a2 = pickle.load(f2)  
  11. >>> a2  
  12. 'apple'  
  13. >>> b2 = pickle.load(f2)  
  14. >>> b2  
  15. {1'One'2'Two'3'Three'}  
  16. >>> c2 = pickle.load(f2)  
  17. >>> c2  
  18. ['fee''fie''foe''fum']  
  19. >>> f2.close()  
 
Pickle 的威力
到目前为止,我们讲述了关于 pickle 方面的基本知识。在这一节,将讨论一些高级问题,当您开始 pickle 复杂对象时,会遇到这些问题,其中包括定制类的实例。幸运的是,Python 可以很容易地处理这种情形。
可移植性
从空间和时间上说,Pickle 是可移植的。换句话说,pickle 文件格式独立于机器的体系结构,这意味着,例如,可以在 Linux 下创建一个 pickle,然后将它发送到在 Windows 或 Mac OS 下运行的 Python 程序。并且,当升级到更新版本的 Python 时,不必担心可能要废弃已有的 pickle。Python 开发人员已经保证 pickle 格式将可以向后兼容 Python 各个版本。事实上,在 pickle 模块中提供了有关目前以及所支持的格式方面的详细信息.
清单 3. 检索所支持的格式
  1. >>> pickle.format_version  
  2. '1.3'  
  3. >>> pickle.compatible_formats  
  4. ['1.0''1.1''1.2']  
 
多个引用,同一对象
在 Python 中,变量是对象的引用。同时,也可以用多个变量引用同一个对象。经证明,Python 在用经过 pickle 的对象维护这种行为方面丝毫没有困难,如清单 4 所示:
清单 4. 对象引用的维护
  1. >>> a = [123]  
  2. >>> b = a  
  3. >>> a  
  4. [123]  
  5. >>> b  
  6. [123]  
  7. >>> a.append(4)  
  8. >>> a  
  9. [1234]  
  10. >>> b  
  11. [1234]  
  12. >>> c = pickle.dumps((a, b))  
  13. >>> d, e = pickle.loads(c)  
  14. >>> d  
  15. [1234]  
  16. >>> e  
  17. [1234]  
  18. >>> d.append(5)  
  19. >>> d  
  20. [12345]  
  21. >>> e  
  22. [12345]  
 
循环引用和递归引用
可以将刚才演示过的对象引用支持扩展到 循环引用(两个对象各自包含对对方的引用)和 递归引用(一个对象包含对其自身的引用)。下面两个清单着重显示这种能力。我们先看一下递归引用:
>清单 5. 递归引用
  1. >>> l = [123]  
  2. >>> l.append(l)  
  3. >>> l  
  4. [123, [...]]  
  5. >>> l[3]  
  6. [123, [...]]  
  7. >>> l[3][3]  
  8. [123, [...]]  
  9. >>> p = pickle.dumps(l)  
  10. >>> l2 = pickle.loads(p)  
  11. >>> l2  
  12. [123, [...]]  
  13. >>> l2[3]  
  14. [123, [...]]  
  15. >>> l2[3][3]  
  16. [123, [...]]  
 
现在,看一个循环引用的示例:
清单 6. 循环引用
  1. >>> a = [12]  
  2. >>> b = [34]  
  3. >>> a.append(b)  
  4. >>> a  
  5. [12, [34]]  
  6. >>> b.append(a)  
  7. >>> a  
  8. [12, [34, [...]]]  
  9. >>> b  
  10. [34, [12, [...]]]  
  11. >>> a[2]  
  12. [34, [12, [...]]]  
  13. >>> b[2]  
  14. [12, [34, [...]]]  
  15. >>> a[2is b  
  16. 1  
  17. >>> b[2is a  
  18. 1  
  19. >>> f = file('temp.pkl''w')  
  20. >>> pickle.dump((a, b), f)  
  21. >>> f.close()  
  22. >>> f = file('temp.pkl''r')  
  23. >>> c, d = pickle.load(f)  
  24. >>> f.close()  
  25. >>> c  
  26. [12, [34, [...]]]  
  27. >>> d  
  28. [34, [12, [...]]]  
  29. >>> c[2]  
  30. [34, [12, [...]]]  
  31. >>> d[2]  
  32. [12, [34, [...]]]  
  33. >>> c[2is d  
  34. 1  
  35. >>> d[2is c  
  36. 1  
 
注意,如果分别 pickle 每个对象,而不是在一个元组中一起 pickle 所有对象,会得到略微不同(但很重要)的结果,如清单 7 所示:
清单 7. 分别 pickle vs. 在一个元组中一起 pickle
  1. >>> f = file('temp.pkl''w')  
  2. >>> pickle.dump(a, f)  
  3. >>> pickle.dump(b, f)  
  4. >>> f.close()  
  5. >>> f = file('temp.pkl''r')  
  6. >>> c = pickle.load(f)  
  7. >>> d = pickle.load(f)  
  8. >>> f.close()  
  9. >>> c  
  10. [12, [34, [...]]]  
  11. >>> d  
  12. [34, [12, [...]]]  
  13. >>> c[2]  
  14. [34, [12, [...]]]  
  15. >>> d[2]  
  16. [12, [34, [...]]]  
  17. >>> c[2is d  
  18. 0  
  19. >>> d[2is c  
  20. 0  
 
相等,但并不总是相同
正如在上一个示例所暗示的,只有在这些对象引用内存中同一个对象时,它们才是相同的。在 pickle 情形中,每个对象被恢复到一个与原来对象相等的对象,但不是同一个对象。换句话说,每个 pickle 都是原来对象的一个副本:
清单 8. 作为原来对象副本的被恢复的对象
  1. >>> j = [123]  
  2. >>> k = j  
  3. >>> k is j  
  4. 1  
  5. >>> x = pickle.dumps(k)  
  6. >>> y = pickle.loads(x)  
  7. >>> y  
  8. [123]  
  9. >>> y == k  
  10. 1  
  11. >>> y is k  
  12. 0  
  13. >>> y is j  
  14. 0  
  15. >>> k is j  
  16. 1  
 
同时,我们看到 Python 能够维护对象之间的引用,这些对象是作为一个单元进行 pickle 的。然而,我们还看到分别调用 dump() 会使 Python 无法维护对在该单元外部进行 pickle 的对象的引用。相反,Python 复制了被引用对象,并将副本和被 pickle 的对象存储在一起。对于 pickle 和恢复单个对象层次结构的应用程序,这是没有问题的。但要意识到还有其它情形。
值得指出的是,有一个选项确实允许分别 pickle 对象,并维护相互之间的引用,只要这些对象都是 pickle 到同一文件即可。 pickle 和 cPickle 模块提供了一个 Pickler (与此相对应是 Unpickler ),它能够跟踪已经被 pickle 的对象。通过使用这个 Pickler ,将会通过引用而不是通过值来 pickle 共享和循环引用:
清单 9. 维护分别 pickle 的对象间的引用
  1. >>> f = file('temp.pkl''w')  
  2. >>> pickler = pickle.Pickler(f)  
  3. >>> pickler.dump(a)  
  4. <cPickle.Pickler object at 0x89b0bb8>  
  5. >>> pickler.dump(b)  
  6. <cPickle.Pickler object at 0x89b0bb8>  
  7. >>> f.close()  
  8. >>> f = file('temp.pkl''r')  
  9. >>> unpickler = pickle.Unpickler(f)  
  10. >>> c = unpickler.load()  
  11. >>> d = unpickler.load()  
  12. >>> c[2]  
  13. [34, [12, [...]]]  
  14. >>> d[2]  
  15. [12, [34, [...]]]  
  16. >>> c[2is d  
  17. 1  
  18. >>> d[2is c  
  19. 1  
 
不可 pickle 的对象
一些对象类型是不可 pickle 的。例如,Python 不能 pickle 文件对象(或者任何带有对文件对象引用的对象),因为 Python 在 unpickle 时不能保证它可以重建该文件的状态(另一个示例比较难懂,在这类文章中不值得提出来)。试图 pickle 文件对象会导致以下错误:
清单 10. 试图 pickle 文件对象的结果
  1. >>> f = file('temp.pkl''w')  
  2. >>> p = pickle.dumps(f)  
  3. Traceback (most recent call last):  
  4.   File "<input>", line 1in ?  
  5.   File "/usr/lib/python2.2/copy_reg.py", line 57in _reduce  
  6.     raise TypeError, "can't pickle %s objects" % base.__name__  
  7. TypeError: can't pickle file objects  
 
类实例
与 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 会调用这些方法。在以下几节中,我们会看到一些示例利用了这些特殊的方法。
现在,我们看一个简单的类实例。首先,创建一个 persist.py 的 Python 模块,它包含以下新型类的定义:
清单 11. 新型类的定义
  1. class Foo(object):  
  2.     def __init__(self, value):  
  3.         self.value = value  
 
现在可以 pickle Foo 实例,并看一下它的表示:
清单 12. pickle Foo 实例
  1. >>> import cPickle as pickle  
  2. >>> from Orbtech.examples.persist import Foo  
  3. >>> foo = Foo('What is a Foo?')  
  4. >>> p = pickle.dumps(foo)  
  5. >>> print p  
  6. ccopy_reg  
  7. _reconstructor  
  8. p1  
  9. (cOrbtech.examples.persist  
  10. Foo  
  11. p2  
  12. c__builtin__  
  13. object  
  14. p3  
  15. NtRp4  
  16. (dp5  
  17. S'value'  
  18. p6  
  19. S'What is a Foo?'  
  20. sb.  
 
可以看到这个类的名称 Foo 和全限定的模块名称 Orbtech.examples.persist 都存储在 pickle 中。如果将这个实例 pickle 成一个文件,稍后再 unpickle 它或在另一台机器上 unpickle,则 Python 会试图导入 Orbtech.examples.persist 模块,如果不能导入,则会抛出异常。如果重命名该类和该模块或者将该模块移到另一个目录,则也会发生类似的错误。
这里有一个 Python 发出错误消息的示例,当我们重命名 Foo 类,然后试图装入先前进行过 pickle 的 Foo 实例时会发生该错误:
清单 13. 试图装入一个被重命名的 Foo 类的经过 pickle 的实例
  1. >>> import cPickle as pickle  
  2. >>> f = file('temp.pkl''r')  
  3. >>> foo = pickle.load(f)  
  4. Traceback (most recent call last):  
  5.   File "<input>", line 1in ?  
  6. AttributeError: 'module' object has no attribute 'Foo'  
 
在重命名 persist.py 模块之后,也会发生类似的错误:
清单 14. 试图装入一个被重命名的 persist.py 模块的经过 pickle 的实例
  1. >>> import cPickle as pickle  
  2. >>> f = file('temp.pkl''r')  
  3. >>> foo = pickle.load(f)  
  4. Traceback (most recent call last):  
  5.   File "<input>", line 1in ?  
  6. ImportError: No module named persist  
 
我们会在下面 模式改进这一节提供一些技术来管理这类更改,而不会破坏现有的 pickle。
特殊的状态方法
前面提到对一些对象类型(譬如,文件对象)不能进行 pickle。处理这种不能 pickle 的对象的实例属性时可以使用特殊的方法( _getstate_() 和 _setstate_() )来修改类实例的状态。这里有一个 Foo 类的示例,我们已经对它进行了修改以处理文件对象属性:
清单 15. 处理不能 pickle 的实例属性
  1. class Foo(object):  
  2.     def __init__(self, value, filename):  
  3.         self.value = value  
  4.         self.logfile = file(filename, 'w')  
  5.     def __getstate__(self):  
  6.         """Return state values to be pickled."""  
  7.         f = self.logfile  
  8.         return (self.value, f.name, f.tell())  
  9.     def __setstate__(self, state):  
  10.         """Restore state from the unpickled state values."""  
  11.         self.value, name, position = state  
  12.         f = file(name, 'w')  
  13.         f.seek(position)  
  14.         self.logfile = f  
 
pickle Foo 的实例时,Python 将只 pickle 当它调用该实例的 _getstate_() 方法时返回给它的值。类似的,在 unpickle 时,Python 将提供经过 unpickle 的值作为参数传递给实例的 _setstate_() 方法。在 _setstate_() 方法内,可以根据经过 pickle 的名称和位置信息来重建文件对象,并将该文件对象分配给这个实例的 logfile 属性。
模式改进
随着时间的推移,您会发现自己必须要更改类的定义。如果已经对某个类实例进行了 pickle,而现在又需要更改这个类,则您可能要检索和更新那些实例,以便它们能在新的类定义下继续正常工作。而我们已经看到在对类或模块进行某些更改时,会出现一些错误。幸运的是,pickle 和 unpickle 过程提供了一些 hook,我们可以用它们来支持这种模式改进的需要。
在这一节,我们将探讨一些方法来预测常见问题以及如何解决这些问题。由于不能 pickle 类实例代码,因此可以添加、更改和除去方法,而不会影响现有的经过 pickle 的实例。出于同样的原因,可以不必担心类的属性。您必须确保包含类定义的代码模块在 unpickle 环境中可用。同时还必须为这些可能导致 unpickle 问题的更改做好规划,这些更改包括:更改类名、添加或除去实例的属性以及改变类定义模块的名称或位置。
类名的更改
要更改类名,而不破坏先前经过 pickle 的实例,请遵循以下步骤。首先,确保原来的类的定义没有被更改,以便在 unpickle 现有实例时可以找到它。不要更改原来的名称,而是在与原来类定义所在的同一个模块中,创建该类定义的一个副本,同时给它一个新的类名。然后使用实际的新类名来替代 NewClassName ,将以下方法添加到原来类的定义中:
清单 16. 更改类名:添加到原来类定义的方法
  1. def __setstate__(self, state):  
  2.     self.__dict__.update(state)  
  3.     self.__class__ = NewClassName  
 
当 unpickle 现有实例时,Python 将查找原来类的定义,并调用实例的 _setstate_() 方法,同时将给新的类定义重新分配该实例的 _class_ 属性。一旦确定所有现有的实例都已经 unpickle、更新和重新 pickle 后,可以从源代码模块中除去旧的类定义。
属性的添加和删除
这些特殊的状态方法 _getstate_() 和 _setstate_() 再一次使我们能控制每个实例的状态,并使我们有机会处理实例属性中的更改。让我们看一个简单的类的定义,我们将向其添加和除去一些属性。这是是最初的定义:
清单 17. 最初的类定义
  1. class Person(object):  
  2.     def __init__(self, firstname, lastname):  
  3.         self.firstname = firstname  
  4.         self.lastname = lastname  
 
假定已经创建并 pickle 了 Person 的实例,现在我们决定真的只想存储一个名称属性,而不是分别存储姓和名。这里有一种方式可以更改类的定义,它将先前经过 pickle 的实例迁移到新的定义:
清单 18. 新的类定义
  1. class Person(object):  
  2.     def __init__(self, fullname):  
  3.         self.fullname = fullname  
  4.     def __setstate__(self, state):  
  5.         if 'fullname' not in state:  
  6.             first = ''  
  7.             last = ''  
  8.             if 'firstname' in state:  
  9.                 first = state['firstname']  
  10.                 del state['firstname']  
  11.             if 'lastname' in state:  
  12.                 last = state['lastname']  
  13.                 del state['lastname']  
  14.             self.fullname = " ".join([first, last]).strip()  
  15.         self.__dict__.update(state)  
 
在这个示例,我们添加了一个新的属性 fullname ,并除去了两个现有的属性 firstname 和 lastname 。当对先前进行过 pickle 的实例执行 unpickle 时,其先前进行过 pickle 的状态会作为字典传递给 _setstate_() ,它将包括 firstname 和 lastname 属性的值。接下来,将这两个值组合起来,并将它们分配给新属性 fullname 。在这个过程中,我们删除了状态字典中旧的属性。更新和重新 pickle 先前进行过 pickle 的所有实例之后,现在可以从类定义中除去 _setstate_() 方法。
模块的修改
在概念上,模块的名称或位置的改变类似于类名称的改变,但处理方式却完全不同。那是因为模块的信息存储在 pickle 中,而不是通过标准的 pickle 接口就可以修改的属性。事实上,改变模块信息的唯一办法是对实际的 pickle 文件本身执行查找和替换操作。至于如何确切地去做,这取决于具体的操作系统和可使用的工具。很显然,在这种情况下,您会想备份您的文件,以免发生错误。但这种改动应该非常简单,并且对二进制 pickle 格式进行更改与对文本 pickle 格式进行更改应该一样有效。
原文地址:点击打开链接


19.4. Pickled Objects 

19.4. Pickle对象 

Probably the biggest limitation of DBM keyed files is in what they can store: data stored under a key must be a simple text string. If you want to store Python objects in a DBM file, you can sometimes manually convert them to and from strings on writes and reads (e.g., with str and eval calls), but this takes you only so far. For arbitrarily complex Python objects such as class instances and nested data structures, you need something more. Class instance objects, for example, cannot be later re-created from their standard string representations. Custom to-string conversions are error prone and not general. 

DBM 键控文件(DBM keyed file)最大的限制也许在于他们可以存储的东西:一个键值下存储的数据必须是个简单文本字符串。如果您想要在DBM文件中储存Python对象,有时您 可以在读写的时候,手动进行与字符串的转换(例如,用str和eval调用),但只能做到这样。对任意复杂的Python对象,如类实例和嵌套的数据结 构,您需要更多的东西。例如,类实例对象以后无法从其标准字符串表达(string representation)重建。自定义的到字符串的转换容易出错,并且不通用。 

The Python pickle module, a standard part of the Python system, provides the conversion step needed. It converts nearly arbitrary Python in-memory objects to and from a single linear string format, suitable for storing in flat files, shipping across network sockets between trusted sources, and so on. This conversion from object to string is often called serializationarbitrary data structures in memory are mapped to a serial string form. 

Python 系统的标准部件,pickle模块,提供了所需的转换步骤。它可以将几乎任意的Python内存对象,转换为单一线性的字符串格式,使之适于无格式文件存 储,或在可靠来源之间跨越网络套接口传输等等,并可反向转换。这种从对象到字符串的转换通常被称为序列化(serialization):将内存中的任意 数据结构映射为串行字符串形式。 

The string representation used for objects is also sometimes referred to as a byte stream, due to its linear format. It retains all the content and references structure of the original in-memory object. When the object is later re-created from its byte string, it will be a new in-memory object identical in structure and value to the original, though located at a different memory address. The re-created object is effectively a copy of the original. 

对象的字符串表达由于其线性的格式,有时也被称为字节流。它包含了原始内存中对象的所有内容和引用结构。当对象后来从其字节串重建时,内存中新建的对象与原对象具有相同的结构和值,但位于不同的内存地址。该重建对象实际上是原对象的复制。 

Pickling works on almost any Python datatypenumbers, lists, dictionaries, class instances, nested structures, and moreand so is a general way to store data. Because pickles contain native Python objects, there is almost no database API to be found; the objects stored are processed with normal Python syntax when they are later retrieved. 

Pickle可用于几乎所有的Python数据类型:数字、列表、字典、类实例、嵌套结构,等等,因此它是存储数据的通用方法。因为pickle包含的是Python本地对象,所以几乎没有数据库的API;对象存储与处理及后来的提取用的都是通常的Python语法。 

19.4.1. Using Object Pickling 

19.4.1. 使用对象pickle 

Pickling may sound complicated the first time you encounter it, but the good news is that Python hides all the complexity of object-to-string conversion. In fact, the pickle module 's interfaces are incredibly simple to use. For example, to pickle an object into a serialized string, we can either make a pickler and call its methods or use convenience functions in the module to achieve the same effect: 

第 一次听到pickle,可能觉得有点复杂,但好消息是,Python隐藏了所有从对象到字符串转换的复杂性。事实上,pickle模块的接口简单易用,简 直令人难以置信。例如,要pickle对象到一个序列化字符串,我们可以生成一个pickler,并调用其方法,或使用模块中的便捷函数来达到相同的效 果: 

 

P = pickle.Pickler( file) 

Make a new pickler for pickling to an open output file object file. 
生成一个新的pickler,用来pickle到一个打开的输出文件对象file。 

 

P.dump( object) 

Write an object onto the pickler's file/stream. 
写一个对象到pickler的文件/流。 

 

pickle.dump( object, file) 

Same as the last two calls combined: pickle an object onto an open file. 
等同于上两个调用的组合:pickle对象到一个打开的文件。 

 

string = pickle.dumps( object) 

Return the pickled representation of object as a character string. 
返回一个字符串作为已pickle对象的表达。 

Unpickling from a serialized string back to the original object is similarboth object and convenience function interfaces are available: 

从一个序列化字符串unpickle回原始对象是类似的,可以用对象也可以用便捷函数接口: 

 

U = pickle.Unpickler( file) 

Make an unpickler for unpickling from an open input file object file. 
生成一个unpickler,用来从一个打开的文件对象file unpickle。 

 

object = U.load( )

Read an object from the unpickler's file/stream. 
从unpickler的文件/流读取一个对象。 

 

object = pickle.load( file) 

Same as the last two calls combined: unpickle an object from an open file. 
等同于上两个调用的组合:从一个打开的文件unpickle一个对象。 

 

object = pickle.loads( string) 

Read an object from a character string rather than a file. 
从字符串读取一个对象,而不是从文件。 

Pickler and Unpickler are exported classes. In all of the preceding cases, file is either an open file object or any object that implements the same attributes as file objects: 

Pickler和Unpickler是导出类。在上述所有情况下,file是个已打开的文件对象,或者是实现了以下文件对象属性的任何对象: 

Pickler calls the file's write method with a string argument. 

Pickler会调用文件的write方法,参数是个字符串。 

Unpickler calls the file's read method with a byte count, and readline without arguments. 

Unpickler会调用文件的read方法,参数是字节数,以及readline,无参数。 

Any object that provides these attributes can be passed in to the file parameters. In particular, file can be an instance of a Python class that provides the read/write methods (i.e., the expected file-like interface). This lets you map pickled streams to in-memory objects with classes, for arbitrary use. For instance, the StringIO standard library module discussed in Chapter 3 provides classes that map file calls to and from in-memory strings. 

任 何提供这些属性的对象都可以作为file参数传入。特别是,file可以是一个提供了读/写方法的Python类实例(即预期的类似文件的接口)。这让您 可以用类映射pickle流到内存对象,并可任意使用。例如,第3章讨论的标准库模块StringIO提供的类,它们可映射文件调用到内存字符串或反之。 

This hook also lets you ship Python objects across a network, by providing sockets wrapped to look like files in pickle calls at the sender, and unpickle calls at the receiver (see the sidebar "Making Sockets Look Like Files," in Chapter 13, for more details). In fact, for some, pickling Python objects across a trusted network serves as a simpler alternative to network transport protocols such as SOAP and XML-RPC; provided that Python is on both ends of the communication (pickled objects are represented with a Python-specific format, not with XML text). 

该 挂钩也可以让您通过网络传输Python对象,只要封装套接口,使之看上去像发送端pickle调用中的文件,以及像接收端unpickle调用中的文件 (详见第13章侧栏“使套接口看上去像文件”)。事实上,对一些人来说,pickle Python对象并在一个值得信赖的网络上传输,是替代如SOAP和XML-RPC之类网络传输协议的一个简单方法;只要通信的两端都有Python(被 pickle的对象是用Python专有的格式表达的,而不是用XML文本)。 

19.4.2. Picking in Action 

19.4.2. Pickle实战 

In more typical use, to pickle an object to a flat file, we just open the file in write mode and call the dump function: 

典型的使用情况是,pickle对象到无格式文件,我们只需以写模式打开文件,并调用dump函数: 

 

% python
>>> table = {'a': [1, 2, 3],
             'b': ['spam', 'eggs'],
             'c': {'name':'bob'}}
>>>
>>> import pickle
>>> mydb  = open('dbase', 'w')
>>> pickle.dump(table, mydb)



Notice the nesting in the object pickled herethe pickler handles arbitrary structures. To unpickle later in another session or program run, simply reopen the file and call load: 

注意这个被pickle对象中的嵌套:pickler可以处理任意结构。然后,在另一个会话或程序中unpickle,只要重新打开该文件,并调用load: 

 

% python
>>> import pickle
>>> mydb  = open('dbase', 'r')
>>> table = pickle.load(mydb)
>>> table
{'b': ['spam', 'eggs'], 'a': [1, 2, 3], 'c': {'name': 'bob'}}



The object you get back from unpickling has the same value and reference structure as the original, but it is located at a different address in memory. This is true whether the object is unpickled in the same or a future process. In Python-speak, the unpickled object is == but is not is: 

unpickle所得的对象具有与原对象相同的值和引用结构,但它位于不同的内存地址。无论在同一进程或另一进程unpickle,都是这样。用Python的话来说,unpickle后的对象是“==”关系,但不是“is”关系: 

 

% python
>>> import pickle
>>> f = open('temp', 'w')
>>> x = ['Hello', ('pickle', 'world')]           # list with nested tuple
>>> pickle.dump(x, f)
>>> f.close( )                                   # close to flush changes
>>>
>>> f = open('temp', 'r')
>>> y = pickle.load(f)
>>> y
['Hello', ('pickle', 'world')]
>>>
>>> x == y, x is y
(True, False)



To make this process simpler still, the module in Example 19-1 wraps pickling and unpickling calls in functions that also open the files where the serialized form of the object is stored. 

为了让这一过程更简单,例19-1中的模块把pickle和unpickle调用封装在函数中,在函数中同时还打开文件,并将对象的序列化存储在该文件中。 

Example 19-1. PP3E\Dbase\filepickle.py

切换行号显示
   1 import pickle
   2 
   3 def saveDbase(filename, object):
   4     file = open(filename, 'w')
   5     pickle.dump(object, file)          # pickle to file
   6     file.close( )                     # any file-like object will do
   7 
   8 def loadDbase(filename):
   9     file = open(filename, 'r')
  10     object = pickle.load(file)         # unpickle from file
  11     file.close( )                     # re-creates object in memory
  12     return object



To store and fetch now, simply call these module functions; here they are in action managing a fairly complex structure with multiple references to the same nested objectthe nested list called L at first is stored only once in the file: 

现在,存储和提取时只需调用这些模块函数;以下实例是个相当复杂的结构,具有对同一嵌套对象的多重引用,该嵌套列表,即第1个L,在文件中只会保存一次: 

 

C:\...\PP3E\Dbase>python
>>> from filepickle import *
>>> L = [0]
>>> D = {'x':0, 'y':L}
>>> table = {'A':L, 'B':D}              # L appears twice
>>> saveDbase('myfile', table)          # serialize to file

C:\...\PP3E\Dbase>python
>>> from filepickle import *
>>> table = loadDbase('myfile')         # reload/unpickle
>>> table
{'B': {'x': 0, 'y': [0]}, 'A': [0]}
>>> table['A'][0] = 1                   # change shared object
>>> saveDbase('myfile', table)          # rewrite to the file

C:\...\PP3E\Dbase>python
>>> from filepickle import *
>>> print loadDbase('myfile')           # both L's updated as expected
{'B': {'x': 0, 'y': [1]}, 'A': [1]}



Besides built-in types like the lists, tuples, and dictionaries of the examples so far, class instances may also be pickled to file-like objects. This provides a natural way to associate behavior with stored data (class methods process instance attributes) and provides a simple migration path (class changes made in module files are automatically picked up by stored instances). Here's a brief interactive demonstration: 

除 了内置的类型,如以上例子中的列表,元组和字典,类实例也可被pickle到类似文件的对象中。这提供了一个自然的方式来关联行为与存储的数据(类方法处 理实例的属性),并提供了一条简单的迁移路径(被存储的实例将自动获得模块文件中对类的更改)。以下是个简短的交互演示: 

 

>>> class Rec:
        def _ _init_ _(self, hours):
            self.hours = hours
        def pay(self, rate=50):
            return self.hours * rate

>>> bob = Rec(40)
>>> import pickle
>>> pickle.dump(bob, open('bobrec', 'w'))
>>>
>>> rec = pickle.load(open('bobrec'))
>>> rec.hours
40
>>> rec.pay( )
2000



We'll explore how this works in more detail in conjunction with shelves later in this chapteras we'll see, although the pickle module can be used directly, it is also the underlying translation engine in both shelves and ZODB databases. 

我们将与本章下面的shelve一起详细探讨这是如此工作的。我们将会看到,虽然pickle模块可直接使用,但它也是shelve和ZODB数据库的底层翻译引擎。 

In fact, Python can pickle just about anything, except for: 

事实上,Python可以pickle任何东西,除了: 

Compiled code objects; functions and classes record just their names in pickles, to allow for later reimport and automatic acquisition of changes made in module files. 

编译的代码对象;函数和类在pickle中只是记录了它们的名字,以便后来重新导入和自动获取模块文件中的更改。 

Instances of classes that do not follow class importability rules (more on this at the end of the section "Shelve Files," later in this chapter). 

不遵守类可导入规则(class importability rule)的类实例(本章后面“Shelve文件”一节的尾部有更多相关内容)。 

Instances of some built-in and user-defined types that are coded in C or depend upon transient operating system states (e.g., open file objects cannot be pickled). 

用C编码的或依赖于操作系统瞬态的一些内置的和用户定义类型的实例(例如,打开的文件对象无法pickle)。 

PicklingError is raised if an object cannot be pickled. 

如果对象不能pickle,会引发PickleError。 

19.4.3. Pickler Protocols and cPickle 

19.4.3. Pickler协议和cPickle 

In recent Python releases, the pickler introduced the notion of protocolsstorage formats for pickled data. Specify the desired protocol by passing an extra parameter to the pickling calls (but not to unpickling calls: the protocol is automatically determined from the pickled data): 

在最近的Python版本中,pickler推出了协议的概念:pickle数据的保存格式。通过pickle调用时传入一个额外的参数,可指定所需的协议(但unpickle调用不需要:协议是自动从已pickle的数据确定的): 

 

pickle.dump(object, file, protocol)



Pickled data may be created in either text or binary protocols. By default, the storage protocol is text (also known as protocol 0). In text mode, the files used to store pickled objects may be opened in text mode as in the earlier examples, and the pickled data is printable ASCII text, which can be read (it's essentially instructions for a stack machine). 

Pickle 数据可以按文本协议或二进制协议产生。默认情况下,存储协议是文本协议(也称为0号协议)。在文本模式下,用来存储pickle对象的文件可以用文本模式 打开,如上述的例子,并且pickle的数据是可打印的ASCII文本,并且是可读的(这基本上是对堆栈机实现的指示)。 

The alternative protocols (protocols 1 and 2) store the pickled data in binary format and require that files be opened in binary mode (e.g., rb, wb). Protocol 1 is the original binary format; protocol 2, added in Python 2.3, has improved support for pickling of new-style classes. Binary format is slightly more efficient, but it cannot be inspected. An older option to pickling calls, the bin argument, has been subsumed by using a pickling protocol higher than 0. The pickle module also provides a HIGHEST_PROTOCOL variable that can be passed in to automatically select the maximum value. 

其 他协议(1号和2号协议 )以二进制格式存储pickle数据,并要求文件以二进制模式打开(例如:rb、wb)。1号协议是原始二进制格式;2号协议是Python 2.3增加的,它改善了对新型类pickle的支持。二进制格式效率更高一点,但它无法进行查看。旧的pickle调用有一个选项,即bin参数,现已被 归入使用大于0的协议。pickle模块还提供了一个HIGHEST_PROTOCOL变量,传入它可以自动选择最大的协议值。 

One note: if you use the default text protocol, make sure you open pickle files in text mode later. On some platforms, opening text data in binary mode may cause unpickling errors due to line-end formats on Windows: 

注意:如果您使用默认的文本协议,以后请务必以文本模式打开pickle文件。在一些平台上,因为Windows的行尾格式不同,以二进制模式打开文本数据可能会导致unpickle错误: 

 

>>> f = open('temp', 'w')                  # text mode file on Windows
>>> pickle.dump(('ex', 'parrot'), f)       # use default text protocol
>>> f.close( ) 
>>>
>>> pickle.load(open('temp', 'r'))         # OK in text mode
('ex', 'parrot')
>>> pickle.load(open('temp', 'rb'))        # fails in binary
Traceback (most recent call last):
  File "<pyshell#337>", line 1, in -toplevel-
    pickle.load(open('temp', 'rb'))
 ...lines deleted...
ValueError: insecure string pickle



One way to sidestep this potential issue is to always use binary mode for your files, even for the text pickle protocol. Since you must open files in binary mode for the binary pickler protocols anyhow (higher than the default 0), this isn't a bad habit to get into: 

回避这个潜在问题的方法之一是,总是使用二进制模式的文件,即使是用文本pickle协议。至少对于二进制pickler协议(高于默认0),您必须以二进制模式打开文件,所以这不是一个坏习惯: 

 

>>> f = open('temp', 'wb')                 # create in binary mode
>>> pickle.dump(('ex', 'parrot'), f)       # use text protocol
>>> f.close( )
>>>
>>> pickle.load(open('temp', 'rb'))
('ex', 'parrot')
>>> pickle.load(open('temp', 'r'))
('ex', 'parrot')



Refer to Python's library manual for more information on the pickler. Also check out marshal, a module that serializes an object too, but can handle only simple object types. pickle is more general than marshal and is normally preferred. 

请参考Python库手册,以了解更多pickler的信息。另外,请查阅marshal,它也是一个序列化对象的模块,但只能处理简单对象类型。pickle比marshal更通用,并通常是首选。 

And while you are flipping (or clicking) through that manual, be sure to also see the entries for the cPickle modulea reimplementation of pickle coded in C for faster performance. You can explicitly import cPickle for a substantial speed boost; its chief limitation is that you cannot subclass its versions of Pickle and Unpickle because they are functions, not classes (this is not required by most programs). The pickle and cPickle modules use compatible data formats, so they may be used interchangeably. 

而 当你翻看(或点击)Python手册时,请一定也要看看cPickle模块的条目,它是pickle的C语言实现,性能上更快。您可以显式导入 cPickle替代pickle,以大幅提升速度;其主要的限制是,你不能继承该版本的Pickle和Unpickle,因为它们是函数,而不是类(多数 程序并不要求它们是类)。pickle和cPickle模块使用兼容的数据格式,所以它们可以互换使用。 

原文地址:点击打开链接



阅读更多
文章标签: Python pickle模块
个人分类: python
上一篇python pprint模块
下一篇Python StringIO模块 实现在内存缓冲区中读写数据
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭