返回介绍

建议44:理解模块 pickle 优劣

发布于 2024-01-30 22:19:09 字数 4072 浏览 0 评论 0 收藏 0

在实际应用中,序列化的场景很常见,如:在磁盘上保存当前程序的状态数据以便重启的时候能够重新加载;多用户或者分布式系统中数据结构的网络传输时,可以将数据序列化后发送给一个可信网络对端,接收者进行反序列化后便可以重新恢复相同的对象;session和cache的存储等。序列化,简单地说就是把内存中的数据结构在不丢失其身份和类型信息的情况下转成对象的文本或二进制表示的过程。对象序列化后的形式经过反序列化过程应该能恢复为原有对象。Python中有很多支持序列化的模块,如pickle、json、marshal和shelve等。最广为人知的为pickle,我们来仔细分析一下这个模块。

pickle估计是最通用的序列化模块了,它还有个C语言的实现cPickle,相比pickle来说具有较好的性能,其速度大概是pickle的1000倍,因此在大多数应用程序中应该优先使用cPickle(注:cPickle除了不能被继承之外,它们两者的使用基本上区别不大,除有特殊情况,本节将不再做具体区分)。pickle中最主要的两个函数对为dump()和load(),分别用来进行对象的序列化和反序列化。

pickle.dump(obj, file[, protocol]):序列化数据到一个文件描述符(一个打开的文件、套接字等)。参数obj表示需要序列化的对象,包括布尔、数字、字符串、字节数组、None、列表、元组、字典和集合等基本数据类型,此外picike还能够处理循环,递归引用对象、类、函数以及类的实例等。参数file支持write()方法的文件句柄,可以为真实的文件,也可以是StringIO对象等。protocol为序列化使用的协议版本,0表示ASCII协议,所序列化的对象使用可打印的ASCII码表示;1表示老式的二进制协议;2表示2.3版本引入的新二进制协议,比以前的更高效。其中协议0和1兼容老版本的Python。protocol默认值为0。

load(file):表示把文件中的对象恢复为原来的对象,这个过程也被称为反序列化。

来看一下load()和dump()的示例。

>>> import cPickle as pickle
>>> my_data = {"name" : "Python", "type" : "Language", "version" : "2.7.5"}
>>> fp = open("picklefile.dat", "wb")  #
打开要写入的文件
>>> pickle.dump(my_data, fp)       #
使用dump
进行序列化
>>> fp.close()
>>>
>>> fp = open("picklefile.dat", "rb")
>>> out = pickle.load(fp)        #
反序列化
>>> print(out)
{'version': '2.7.5', 'type': 'Language', 'name': 'Python'}
>>> fp.close()

pickle之所以能成为通用的序列化模块,与其良好的特性是分不开的,总结为以下几点:

1)接口简单,容易使用。通过dump()和load()便可轻易实现序列化和反序列化。

2)pickle的存储格式具有通用性,能够被不同平台的Python解析器共享,比如,Linux下序列化的格式文件可以在Windows平台的Python解析器上进行反序列化,兼容性较好。

3)支持的数据类型广泛。如数字、布尔值、字符串,只包含可序列化对象的元组、字典、列表等,非嵌套的函数、类以及通过类的__dict__或者__getstate__()可以返回序列化对象的实例等。

4)pickle模块是可以扩展的。对于实例对象,pickle在还原对象的时候一般是不调用__init__()函数的,如果要调用__init__()进行初始化,对于古典类可以在类定义中提供__getinitargs__()函数,并返回一个元组,当进行unpickle的时候,Python就会自动调用__init__(),并把__getinitargs__()中返回的元组作为参数传递给__init__(),而对于新式类,可以提供__getnewargs__()来提供对象生成时候的参数,在unpickle的时候以Class.__new__(Class, *arg)的方式创建对象。对于不可序列化的对象,如sockets、文件句柄、数据库连接等,也可以通过实现pickle协议来解决这些局限,主要是通过特殊方法__getstate__()和__setstate__()来返回实例在被pickle时的状态。来看以下示例:

import cPickle as pickle
class TextReader:
  def __init__(self, filename):
    self.filename = filename        #
文件名称
    self.file = open(filename)        #
打开文件的句柄
    self.postion = self.file.tell()     #
文件的位置
  def readline(self):
    line = self.file.readline()
    self.postion = self.file.tell()
    if not line:
      return None
    if line.endswith('\n'):
      line = line[:-1]
    return "%i: %s" % (self.postion, line)
  def __getstate__(self):           #
记录文件被pickle
时候的状态
    state = self.__dict__.copy()      #
获取被pickle
时的字典信息
    del state['file']
    return state
  def __setstate__(self, state):        #
设置反序列化后的状态     
     self.__dict__.update(state)
    file = open(self.filename)
    self.file = file
reader = TextReader("zen.txt")
print(reader.readline())
print(reader.readline())
s = pickle.dumps(reader)            #
在dumps
的时候会默认调用__getstate__
new_reader = pickle.loads(s)          #
在loads
的时候会默认调用__setstate__
print(new_reader.readline())

5)能够自动维护对象间的引用,如果一个对象上存在多个引用,pickle后不会改变对象间的引用,并且能够自动处理循环和递归引用。

>>> a = ['a','b']
>>> b = a                    #b
引用对象a
>>> b.append('c')
>>> p = pickle.dumps((a,b))
>>> a1,b1 = pickle.loads(p)
>>> a1
['a', 'b', 'c']
>>> b1
['a', 'b', 'c']
>>> a1.append('d')               #
反序列化对a1
对象的修改仍然会影响到b1
>>> b1
['a', 'b', 'c', 'd']

但pickle使用也存在以下一些限制:

pickle不能保证操作的原子性。pickle并不是原子操作,也就是说在一个pickle调用中如果发生异常,可能部分数据已经被保存,另外如果对象处于深递归状态,那么可能超出Python的最大递归深度。递归深度可以通过sys.setrecursionlimit()进行扩展。

pickle存在安全性问题。Python的文档清晰地表明它不提供安全性保证,因此对于一个从不可信的数据源接收的数据不要轻易进行反序列化。由于loads()可以接收字符串作为参数,这意味着精心设计的字符串给入侵提供了一种可能。在Pthon解释器中输入代码pickle.loads("cos\nsystem\n(S'dir'\ntR.")便可查看当前目录下所有文件。如果将dir替换为其他更具有破坏性的命令将会带来安全隐患。如果要进一步提高安全性,用户可以通过继承类pickle.Unpickler并重写find_class()方法来实现。

pickle协议是Python特定的,不同语言之间的兼容性难以保障。用Python创建的pickle文件可能其他语言不能使用,如Perl、PHP、Java等。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文