>>> from env_helper import info; info()
页面更新时间: 2023-12-27 09:30:27
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

4.8. 理解模块pickle优劣

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

Python中有很多支持序列化的模块,如 picklejsonmarshapshelve 等。 最广为人知的为pickle,我们来仔细分析一下这个模块。

pickk估计是最通用的序列化模块了,它还有个C语言的实现 cPickle , 相比 pickle来说 具有较好的性能, 其速度大概是pickle的 1000 倍,因此在大多数应用程序中应该优先使用 cpikle

注:cPickle除了不能被继承之外,它们两者的使用基本上区别不大,除有持殊情况, 本节将不再做具体区分。

pickle中最主要的两个函数对为 dump()load() ,分別用来进行对 象的序列化和反序列化。

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

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

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

>>> import pickle
>>> my_data={"name":"python","type":"language","version":"2.7.5"}
>>> fq=open("pick.dat","wb")
>>> pickle.dump(my_data,fq)
>>> fq.close()
>>> fp=open("pick.dat","rb")
>>> out=pickle.load(fp)
>>> print(out)
{'name': 'python', 'type': 'language', 'version': '2.7.5'}

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

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

  2. pickle的存储格式具有通用性.能够被不同平台的Pythcm解析器共享,比如,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协议来解决这些局限,主要是通过特殊方法 __gestate__()__setstate__() 来返回实例在被pickle时的状态。

来看以下示例:

>>> import 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()#  If 获取被 pickle 时的字典信息
>>>         del state['file']
>>>         return state
>>>
>>>     def __setstat__(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)   # fr 在 dumps 的时候会默认调用 getstate_
>>> newsreader = pickle.loads(s) # 在 loads 的时候会默认调用 setstate_
>>> #print(newsreader.readline())
4:asdf
None

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

>>> a=['a','b']
>>> 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')
>>> 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等。