pickle详解

pickle详解

之前在网上收集了点资料。放上来。

持久性

对象持久性

Pickle

  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 文件中的对象。
>>> 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() 来以同样的顺序检索这些对象。
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.format_version '1.3' >>> pickle.compatible_formats ['1.0', '1.1', '1.2'] 

多个引用,同一对象

>>> 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] 

循环引用和递归引用

>>> 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, [...]] 

循环引用:

>>> 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 所有对象,会得到略微不同(但很重要)的结果
分别 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 >>> d[2] is c 0 

   相等,但并不总是相同。
   正如在上一个示例所暗示的,只有在这些对象引用内存中同一个对象时,它们才是相同的。在 pickle 情形中,每个对象被恢复到一个与原来对象相等的对象,但不是同一个对象。换句话说,每个 pickle 都是原来对象的一个副本:
作为原来对象副本的被恢复的对象:




>>> j = [1, 2, 3] >>> k = j >>> k is j 1 >>> x = pickle.dumps(k) >>> y = pickle.loads(x) >>> y [1, 2, 3] >>> y == k 1 >>> y is k 0 >>> y is j 0 >>> k is j 1 
>>> f = file('temp.pkl', 'w') >>> pickler = pickle.Pickler(f) >>> pickler.dump(a) 
  
    0x89b0bb8> 
   >>> pickler.dump(b) 
   
     0x89b0bb8> 
    >>> f.close() 
    >>> f = file( 
    'temp.pkl', 
    'r') 
    >>> unpickler = pickle.Unpickler(f) 
    >>> c = unpickler.load() 
    >>> d = unpickler.load() 
    >>> c[ 
    2] [ 
    3, 
    4, [ 
    1, 
    2, [...]]] 
    >>> d[ 
    2] [ 
    1, 
    2, [ 
    3, 
    4, [...]]] 
    >>> c[ 
    2] 
    is d 
    1 
    >>> d[ 
    2] 
    is c 
    1 
    
  

不可 pickle 的对象

>>> f = file('temp.pkl', 'w') >>> p = pickle.dumps(f) Traceback (most recent call last): File "", line 1, in ? File "/usr/lib/python2.2/copy_reg.py", line 57, in _reduce raise TypeError, "can't pickle %s objects" % base.__name__ 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 模块,它包含以下新型类的定义:
新型类的定义










class Foo(object): def __init__(self, value): self.value = value 
>>> import cPickle as pickle >>> from Orbtech.examples.persist import Foo >>> foo = Foo('What is a Foo?') >>> p = pickle.dumps(foo) >>> print p ccopy_reg _reconstructor p1 (cOrbtech.examples.persist Foo p2 c__builtin__ object p3 NtRp4 (dp5 S'value' p6 S'What is a Foo?' sb. 
>>> import cPickle as pickle >>> f = file('temp.pkl', 'r') >>> foo = pickle.load(f) Traceback (most recent call last): File "", line 1, in ? AttributeError: 'module' object has no attribute 'Foo' 
>>> import cPickle as pickle >>> f = file('temp.pkl', 'r') >>> foo = pickle.load(f) Traceback (most recent call last): File "", line 1, in ? ImportError: No module named persist 

  我们会在下面 模式改进这一节提供一些技术来管理这类更改,而不会破坏现有的 pickle。

pickle类实例的特殊的状态方法

  前面提到对一些对象类型(譬如,文件对象)不能进行 pickle。处理这种不能 pickle 的对象的实例属性时可以使用特殊的方法( getstate() 和 setstate() )来修改类实例的状态。这里有一个 Foo 类的示例,我们已经对它进行了修改以处理文件对象属性:
处理不能 pickle 的实例属性:

class Foo(object): def __init__(self, value, filename): self.value = value self.logfile = file(filename, 'w') def __getstate__(self): """Return state values to be pickled.""" f = self.logfile return (self.value, f.name, f.tell()) def __setstate__(self, state): """Restore state from the unpickled state values.""" self.value, name, position = state f = file(name, 'w') f.seek(position) self.logfile = f 

  pickle Foo 的实例时,Python 将只 pickle 当它调用该实例的 getstate() 方法时返回给它的值。类似的,在 unpickle 时,Python 将提供经过 unpickle 的值作为参数传递给实例的 setstate() 方法。在 setstate() 方法内,可以根据经过 pickle 的名称和位置信息来重建文件对象,并将该文件对象分配给这个实例的 logfile 属性。

模式改进

类名的更改

def __setstate__(self, state): self.__dict__.update(state) self.__class__ = NewClassName 

  当 unpickle 现有实例时,Python 将查找原来类的定义,并调用实例的 setstate() 方法,同时将给新的类定义重新分配该实例的 class 属性。一旦确定所有现有的实例都已经 unpickle、更新和重新 pickle 后,可以从源代码模块中除去旧的类定义。

属性的添加和删除

  这些特殊的状态方法 getstate() 和 setstate() 再一次使我们能控制每个实例的状态,并使我们有机会处理实例属性中的更改。让我们看一个简单的类的定义,我们将向其添加和除去一些属性。
最初的类定义:

class Person(object): def __init__(self, firstname, lastname): self.firstname = firstname self.lastname = lastname
class Person(object): def __init__(self, fullname): self.fullname = fullname def __setstate__(self, state): if 'fullname' not in state: first = '' last = '' if 'firstname' in state: first = state['firstname'] del state['firstname'] if 'lastname' in state: last = state['lastname'] del state['lastname'] self.fullname = " ".join([first, last]).strip() self.__dict__.update(state) 

  在这个示例,我们添加了一个新的属性 fullname ,并除去了两个现有的属性 firstname 和 lastname 。当对先前进行过 pickle 的实例执行 unpickle 时,其先前进行过 pickle 的状态会作为字典传递给 setstate() ,它将包括 firstname 和 lastname 属性的值。接下来,将这两个值组合起来,并将它们分配给新属性 fullname 。在这个过程中,我们删除了状态字典中旧的属性。更新和重新 pickle 先前进行过 pickle 的所有实例之后,现在可以从类定义中除去 setstate() 方法。

模块的修改

  在概念上,模块的名称或位置的改变类似于类名称的改变,但处理方式却完全不同。那是因为模块的信息存储在 pickle 中,而不是通过标准的 pickle 接口就可以修改的属性。事实上,改变模块信息的唯一办法是对实际的 pickle 文件本身执行查找和替换操作。至于如何确切地去做,这取决于具体的操作系统和可使用的工具。很显然,在这种情况下,您会想备份您的文件,以免发生错误。但这种改动应该非常简单,并且对二进制 pickle 格式进行更改与对文本 pickle 格式进行更改应该一样有效。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/220226.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午8:57
下一篇 2026年3月17日 下午8:57


相关推荐

  • mysql怎么导出insert语句_mysql导出insert语句

    mysql怎么导出insert语句_mysql导出insert语句disable keys 告诉 mysqldump 在 INSERT 语句的开头和结尾增加 40000ALTERTA 和 40000ALTERTA 导出一个数据库结构 mysqldump uwcnc p d add drop tablesmgp apps w

    2026年3月16日
    2
  • 基于DeepSeek与Cherry Studio搭建个人AI知识库的完整教程与源码实践

    基于DeepSeek与Cherry Studio搭建个人AI知识库的完整教程与源码实践

    2026年3月15日
    2
  • qtabwidget切换tab_qt tablewidget

    qtabwidget切换tab_qt tablewidget0.实现效果(声明:这只是个测试,不是很满意,放着也没用就分享下)实现效果GIF:完整代码链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qt/MyTabWidget相关参考:https://www.cnblogs.com/findumars/p/5175984.html相关参考:https://github.com/MRXY001/Qt-DragableTabWidget1.实现过程QTabWidget的Tab

    2026年2月26日
    4
  • mybatismysql批量insert数据_mysql数据库简介

    mybatismysql批量insert数据_mysql数据库简介MySQL批量插入

    2022年10月5日
    4
  • 大数据Kafka(四):kafka的shell命令使用

    大数据Kafka(四):kafka的shell命令使用全网最详细的大数据 Kafka 文章系列 强烈建议收藏加关注 新文章都已经列出历史文章目录 帮助大家回顾前面的知识重点 目录系列历史文章 Kafka 的 shell 命令使用一 创建 topic 二 生产消息到 kafka 三 从 kafka 中消费消息四 查看主题的命令五 运行 describe 的命令六 增加 topic 分区数七 删除 topic 八 使用 kafkaTools 操作 Kafka 系列历史文章 2021 年大数据 Kafka 四 kafka 的 shell 命令使用

    2026年3月18日
    3
  • 视频直播连麦技术详解「建议收藏」

    视频直播连麦技术详解「建议收藏」前言:随着带宽、Wifi的不断升级,手机的普及,直播技术不断突破,各种门槛在降低,全民直播时代已经来临。直播也深入到各行各业,比如说在线教育,还有财经行业等,也渐渐成为各行各业的标配。云帆加速自成立以来就一直致力于流媒体领域企业服务,尤其对于直播,目前已经推出了针对于不同场景的直播云解决方案,在保证广大用户使用体验的前提下,为客户节省更多的研发成本。无论是传统企业转型,或者是创业企业,云帆加速都…

    2022年7月24日
    35

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号