>>> 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中有很多支持序列化的模块,如 pickle
、 json
、 marshap
和 shelve
等。 最广为人知的为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之所以能成为通用的序列化模块,与其良好的特性是分不开的,总结为以下几点:
接口简单,容易使用。通过
dump()
和load()
便可轻易实现序列化和反序列化。pickle的存储格式具有通用性.能够被不同平台的Pythcm解析器共享,比如,Linux 下序列化的格式文件可以在Windows平台的Python解析器上进行反序列化,兼容性较好
支持的数据类型广泛。如数字、布尔值、字符串,只包含可序列化对象的元组、字 典、列表等,非嵌套的函数、类以及通过类的
_dict_
或者_getstate_
可以返回序列化对 象的实例等。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等。