- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议44:理解模块 pickle 优劣
在实际应用中,序列化的场景很常见,如:在磁盘上保存当前程序的状态数据以便重启的时候能够重新加载;多用户或者分布式系统中数据结构的网络传输时,可以将数据序列化后发送给一个可信网络对端,接收者进行反序列化后便可以重新恢复相同的对象;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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论