翼度科技»论坛 编程开发 python 查看内容

Python中关于对象序列化实现和原理

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
pickle模块可以实现任意的Python对象转换为一系列字节(即序列化对象)的算法。这些字节流可以被传输或存储,接着也可以重构为—个和原先对象具有相同特征的新对象。
注意:

  • pickle的文档清晰的表明它不提供安全保证。实际上,反序列化后可以执行任意代码,所以慎用
  • pickle来作为内部进程通信或者数据存储,也不要相信那些你不能验证安全性的数据。
  • hmac模块,它提供了—个以安全方式验证序列化数据源的示例。
字符串的编码和解码

第一个示例是使用 dumps() 将一个数据结构编码为一个字符串,然后将其输出到控制台。它使用内置类型组成的数据结构,其实任何类的实例都可以被序列化,如后面的例子所示。
  1. import pickle
  2. import pprint
  3. data = [{'a': 'A', 'b': 2, 'c': 3.0}]
  4. print('DATA:', end=' ')
  5. pprint.pprint(data)
  6. data_string = pickle.dumps(data)
  7. print('PICKLE: {!r}'.format(data_string))
复制代码
默认情况下,Python 3 的序列化以兼容的二进制形式进行。
  1. $ python3 pickle_string.py
  2. DATA: [{'a': 'A', 'b': 2, 'c': 3.0}]
  3. PICKLE: b'\x80\x03]q\x00}q\x01(X\x01\x00\x00\x00cq\x02G@\x08\x00
  4. \x00\x00\x00\x00\x00X\x01\x00\x00\x00bq\x03K\x02X\x01\x00\x00\x0
  5. 0aq\x04X\x01\x00\x00\x00Aq\x05ua.'
复制代码
一旦数据被序列化,你就可以把它写入到文件、socket、管道等等中。之后你可以读取这个文件,反序列化这些数据来构造具有相同值的新对象。
  1. import pickle
  2. import pprint
  3. data1 = [{'a': 'A', 'b': 2, 'c': 3.0}]
  4. print('BEFORE: ', end=' ')
  5. pprint.pprint(data1)
  6. data1_string = pickle.dumps(data1)
  7. data2 = pickle.loads(data1_string)
  8. print('AFTER : ', end=' ')
  9. pprint.pprint(data2)
  10. print('SAME? :', (data1 is data2))
  11. print('EQUAL?:', (data1 == data2))
复制代码
新对象和之前的对象相等,但不是之前的对象。
  1. $ python3 pickle_unpickle.py
  2. BEFORE:  [{'a': 'A', 'b': 2, 'c': 3.0}]
  3. AFTER :  [{'a': 'A', 'b': 2, 'c': 3.0}]
  4. SAME? : False
  5. EQUAL?: True
复制代码
流的序列化

pickle 除了提供 dumps() 和 loads() ,还提供了非常方便的函数用于操作文件流。支持同时写多个对象到同一个流中,然后在不知道有多少个对象或不知道它们有多大时,能够从这个流中读取到这些对象。
pickle_stream.py
  1. import io
  2. import pickle
  3. import pprint
  4. class SimpleObject:
  5.     def __init__(self, name):
  6.         self.name = name
  7.         self.name_backwards = name[::-1]
  8.         return
  9. data = []
  10. data.append(SimpleObject('pickle'))
  11. data.append(SimpleObject('preserve'))
  12. data.append(SimpleObject('last'))
  13. # 模拟一个文件
  14. out_s = io.BytesIO()
  15. # 写入流中
  16. for o in data:
  17.     print('WRITING : {} ({})'.format(o.name, o.name_backwards))
  18.     pickle.dump(o, out_s)
  19.     out_s.flush()
  20. # 设置一个可读取的流
  21. in_s = io.BytesIO(out_s.getvalue())
  22. # 读取数据
  23. while True:
  24.     try:
  25.         o = pickle.load(in_s)
  26.     except EOFError:
  27.         break
  28.     else:
  29.         print('READ    : {} ({})'.format(
  30.             o.name, o.name_backwards))
复制代码
这个例子使用两个 BytesIO 缓冲区来模拟流。一个接收序列化对象,另一个通过 load() 方法读取第一个的值。一个简单的数据库格式也可以使用序列化来存储对象。 shelve 模块就是这样使用的一个范例。
  1. $ python3 pickle_stream.py
  2. WRITING : pickle (elkcip)
  3. WRITING : preserve (evreserp)
  4. WRITING : last (tsal)
  5. READ    : pickle (elkcip)
  6. READ    : preserve (evreserp)
  7. READ    : last (tsal)
复制代码
除了用于存储数据,序列化在用于内部进程通信时也是非常灵活的。比如,使用 os.fork() 和 os.pipe() ,可以建立一些工作进程,它们从一个管道中读取任务说明并把结果输出到另一个管道。操作这些工作池、发送任务和接受返回的核心代码可以复用,因为任务和返回对象不是一个特殊的类。如果使用管道或者套接字,就不要忘记在序列化每个对象后刷新它们,并通过它们之间的连接将数据推送到另外一端。查看 multiprocessing 模块构建一个可复用的任务池管理器。
重建对象的问题

在处理自定义类时,你应该保证这些被序列化的类会在进程命名空间出现 只有数据实例才能被序列化,而不能是定义的类。在反序列化时,类的名字被用于寻找构造器以便创建新对象。接下来这个例子,是将一个类实例写入到文件中。
  1. pickle_dump_to_file_1.py
  2. import pickle
  3. import sys
  4. class SimpleObject:
  5.     def __init__(self, name):
  6.         self.name = name
  7.         l = list(name)
  8.         l.reverse()
  9.         self.name_backwards = ''.join(l)
  10. if __name__ == '__main__':
  11.     data = []
  12.     data.append(SimpleObject('pickle'))
  13.     data.append(SimpleObject('preserve'))
  14.     data.append(SimpleObject('last'))
  15.     filename = sys.argv[1]
  16.     with open(filename, 'wb') as out_s:
  17.         for o in data:
  18.             print('WRITING: {} ({})'.format(
  19.                 o.name, o.name_backwards))
  20.             pickle.dump(o, out_s)
复制代码
当我运行这个脚本时,它会创建名为我在命令行中输入的参数的文件。
  1. $ python3 pickle_dump_to_file_1.py test.dat
  2. WRITING: pickle (elkcip)
  3. WRITING: preserve (evreserp)
  4. WRITING: last (tsal)
复制代码
之后尝试将刚才的序列化的结果对象装载进来是失败的。
  1. pickle_load_from_file_1.py
  2. import pickle
  3. import pprint
  4. import sys
  5. filename = sys.argv[1]
  6. with open(filename, 'rb') as in_s:
  7.     while True:
  8.         try:
  9.             o = pickle.load(in_s)
  10.         except EOFError:
  11.             break
  12.         else:
  13.             print('READ: {} ({})'.format(
  14.                 o.name, o.name_backwards))
复制代码
这个版本失败了,因为这里没有可用的 SimpleObject 类。
  1. $ python3 pickle_load_from_file_1.py test.dat
  2. Traceback (most recent call last):
  3.   File "pickle_load_from_file_1.py", line 15, in <module>
  4.     o = pickle.load(in_s)
  5. AttributeError: Can't get attribute 'SimpleObject' on <module '_
  6. _main__' from 'pickle_load_from_file_1.py'>
复制代码
下面是正确的版本,它从一开始的脚本中导入了 SimpleObject 类。添加导入语句可以让该脚本找到类并构建对象。
  1. from pickle_dump_to_file_1 import SimpleObject
复制代码
现在运行修改后的脚本可以得到预期的结果了。
  1. $ python3 pickle_load_from_file_2.py test.dat
  2. READ: pickle (elkcip)
  3. READ: preserve (evreserp)
  4. READ: last (tsal)
复制代码
无法序列化的对象

不是所有对象都可以被序列化的。如套接字、文件句柄、数据库连接或其他具有运行时状态的对象,可能依赖于操作系统或其他进程无法有效的存储下来。那些不能被序列化的类可以定义_getstate_和__setstate__()方法来返回实例在被序列化时的状态。
_getstate_()方法须返回一个包含该对象内部状态的对象。一种便捷的方式是使用字典,字典的值可以是任意可序列化对象。然后状态会被存储,当对象序列化时传递给__setstate__方法。
pickle_state.py
  1. import pickle
  2. class State:
  3.     def __init__(self, name):
  4.         self.name = name
  5.     def __repr__(self):
  6.         return 'State({!r})'.format(self.__dict__)
  7. class MyClass:
  8.     def __init__(self, name):
  9.         print('MyClass.__init__({})'.format(name))
  10.         self._set_name(name)
  11.     def _set_name(self, name):
  12.         self.name = name
  13.         self.computed = name[::-1]
  14.     def __repr__(self):
  15.         return 'MyClass({!r}) (computed={!r})'.format(
  16.             self.name, self.computed)
  17.     def __getstate__(self):
  18.         state = State(self.name)
  19.         print('__getstate__ -> {!r}'.format(state))
  20.         return state
  21.     def __setstate__(self, state):
  22.         print('__setstate__({!r})'.format(state))
  23.         self._set_name(state.name)
  24. inst = MyClass('name here')
  25. print('Before:', inst)
  26. dumped = pickle.dumps(inst)
  27. reloaded = pickle.loads(dumped)
  28. print('After:', reloaded)
复制代码
这个例子使用一个单独的 State对象存储MyClass的内部状态。当 MyClass的实例反序列化时,会给_setstate_()传入一个 state的实例去初始化新的对象。
如果__getstate__()返回值是false,则__setstate__()在对象反序列化时不会被调用。
循环引用

序列化协议会自动处理对象间的循环引用,所以即使复杂的数据结构也不需要去特殊处理。考虑下图,它包含了多个循环,但正确的结构仍然能被反序列化输出。
序列化一个循环引用的数据结构
pickle_cycle.py
  1. import pickle
  2. class Node:
  3.     """一个简单的有向图
  4.     """
  5.     def __init__(self, name):
  6.         self.name = name
  7.         self.connections = []
  8.     def add_edge(self, node):
  9.          """在这个节点和其他节点间建立一条边
  10.                  """
  11.         self.connections.append(node)
  12.     def __iter__(self):
  13.         return iter(self.connections)
  14. def preorder_traversal(root, seen=None, parent=None):
  15.     """给一个图生成边的生成器函数
  16.     """
  17.     if seen is None:
  18.         seen = set()
  19.     yield (parent, root)
  20.     if root in seen:
  21.         return
  22.     seen.add(root)
  23.     for node in root:
  24.         recurse = preorder_traversal(node, seen, root)
  25.         for parent, subnode in recurse:
  26.             yield (parent, subnode)
  27. def show_edges(root):
  28.      """打印输出图的所有边
  29.          """
  30.     for parent, child in preorder_traversal(root):
  31.         if not parent:
  32.             continue
  33.         print('{:>5} -> {:>2} ({})'.format(
  34.             parent.name, child.name, id(child)))
  35. # 创建有向图
  36. root = Node('root')
  37. a = Node('a')
  38. b = Node('b')
  39. c = Node('c')
  40. # 给节点间添加边
  41. root.add_edge(a)
  42. root.add_edge(b)
  43. a.add_edge(b)
  44. b.add_edge(a)
  45. b.add_edge(c)
  46. a.add_edge(a)
  47. print('ORIGINAL GRAPH:')
  48. show_edges(root)
  49. #学习中遇到问题没人解答?小编创建了一个Python学习交流群:711312441
  50. # 序列化和反序列化有向图
  51. # 产生一组新的节点
  52. dumped = pickle.dumps(root)
  53. reloaded = pickle.loads(dumped)
  54. print('\nRELOADED GRAPH:')
  55. show_edges(reloaded)
复制代码
经过序列化和反序列化,这些新的有向图节点对象并不是一开始创建的那些对象,但对象之间的关系保持不变,这可以通过检查对象 id() 返回的值验证。
  1. $ python3 pickle_cycle.py
  2. ORIGINAL GRAPH:
  3. root ->  a (4315798272)
  4.     a ->  b (4315798384)
  5.     b ->  a (4315798272)
  6.     b ->  c (4315799112)
  7.     a ->  a (4315798272)
  8. root ->  b (4315798384)
  9. RELOADED GRAPH:
  10. root ->  a (4315904096)
  11.     a ->  b (4315904152)
  12.     b ->  a (4315904096)
  13.     b ->  c (4315904208)
  14.     a ->  a (4315904096)
  15. root ->  b (4315904152
复制代码
来源:https://www.cnblogs.com/python1111/p/17953067
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
来自手机

举报 回复 使用道具