8.2. pickle — 对象序列化 | 数据持久和数据交换 |《python 3 标准库实例教程》| python 技术论坛-金年会app官方网

未匹配的标注

目的:对象序列化

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

警告

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

字符串的编码和解码

第一个示例是使用 dumps() 将一个数据结构编码为一个字符串,然后将其输出到控制台。它使用内置类型组成的数据结构,其实任何类的实例都可以被序列化,如后面的例子所示。

pickle_string.py

import pickle
import pprint
data = [{'a': 'a', 'b': 2, 'c': 3.0}]
print('data:', end=' ')
pprint.pprint(data)
data_string = pickle.dumps(data)
print('pickle: {!r}'.format(data_string))

默认情况下,python 3的序列化以兼容的二进制形式进行。

$ python3 pickle_string.py
data: [{'a': 'a', 'b': 2, 'c': 3.0}]
pickle: b'\x80\x03]q\x00}q\x01(x\x01\x00\x00\x00cq\x02g@\x08\x00
\x00\x00\x00\x00\x00x\x01\x00\x00\x00bq\x03k\x02x\x01\x00\x00\x0
0aq\x04x\x01\x00\x00\x00aq\x05ua.'

一旦数据被序列化,你就可以把它写入到文件、socket、管道等等中。之后你可以读取这个文件,反序列化这些数据来构造具有相同值的新对象。

pickle_unpickle.py

import pickle
import pprint
data1 = [{'a': 'a', 'b': 2, 'c': 3.0}]
print('before: ', end=' ')
pprint.pprint(data1)
data1_string = pickle.dumps(data1)
data2 = pickle.loads(data1_string)
print('after : ', end=' ')
pprint.pprint(data2)
print('same? :', (data1 is data2))
print('equal?:', (data1 == data2))

新对象和之前的对象相等,但不是之前的对象。

$ python3 pickle_unpickle.py
before:  [{'a': 'a', 'b': 2, 'c': 3.0}]
after :  [{'a': 'a', 'b': 2, 'c': 3.0}]
same? : false
equal?: true

流的序列化

 pickle  除了提供  dumps() 和  loads() ,还提供了非常方便的函数用于操作文件流。支持同时写多个对象到同一个流中,然后在不知道有多少个对象或不知道它们有多大时,能够从这个流中读取到这些对象。

pickle_stream.py

import io
import pickle
import pprint
class simpleobject:
    def __init__(self, name):
        self.name = name
        self.name_backwards = name[::-1]
        return
data = []
data.append(simpleobject('pickle'))
data.append(simpleobject('preserve'))
data.append(simpleobject('last'))
# 模拟一个文件
out_s = io.bytesio()
# 写入流中
for o in data:
    print('writing : {} ({})'.format(o.name, o.name_backwards))
    pickle.dump(o, out_s)
    out_s.flush()
# 设置一个可读取的流
in_s = io.bytesio(out_s.getvalue())
# 读取数据
while true:
    try:
        o = pickle.load(in_s)
    except eoferror:
        break
    else:
        print('read    : {} ({})'.format(
            o.name, o.name_backwards))

这个例子使用两个 bytesio 缓冲区来模拟流。一个接收序列化对象,另一个通过 load() 方法读取第一个的值。一个简单的数据库格式也可以使用序列化来存储对象。   模块就是这样使用的一个范例。

$ python3 pickle_stream.py
writing : pickle (elkcip)
writing : preserve (evreserp)
writing : last (tsal)
read    : pickle (elkcip)
read    : preserve (evreserp)
read    : last (tsal)

除了用于存储数据,序列化在用于内部进程通信时也是非常灵活的。比如,使用  os.fork()os.pipe() ,可以建立一些工作进程,它们从一个管道中读取任务说明并把结果输出到另一个管道。操作这些工作池、发送任务和接受返回的核心代码可以复用,因为任务和返回对象不是一个特殊的类。如果使用管道或者套接字,就不要忘记在序列化每个对象后刷新它们,并通过它们之间的连接将数据推送到另外一端。查看 模块构建一个可复用的任务池管理器。

重建对象的问题

在处理自定义类时,你应该保证这些被序列化的类会在进程命名空间出现 。只有数据实例才能被序列化,而不能是定义的类。在反序列化时,类的名字被用于寻找构造器以便创建新对象。接下来这个例子,是将一个类实例写入到文件中。

pickle_dump_to_file_1.py

import pickle
import sys
class simpleobject:
    def __init__(self, name):
        self.name = name
        l = list(name)
        l.reverse()
        self.name_backwards = ''.join(l)
if __name__ == '__main__':
    data = []
    data.append(simpleobject('pickle'))
    data.append(simpleobject('preserve'))
    data.append(simpleobject('last'))
    filename = sys.argv[1]
    with open(filename, 'wb') as out_s:
        for o in data:
            print('writing: {} ({})'.format(
                o.name, o.name_backwards))
            pickle.dump(o, out_s)

当我运行这个脚本时,它会创建名为我在命令行中输入的参数的文件。

$ python3 pickle_dump_to_file_1.py test.dat
writing: pickle (elkcip)
writing: preserve (evreserp)
writing: last (tsal)

之后尝试将刚才的序列化的结果对象装载进来是失败的。

pickle_load_from_file_1.py

import pickle
import pprint
import sys
filename = sys.argv[1]
with open(filename, 'rb') as in_s:
    while true:
        try:
            o = pickle.load(in_s)
        except eoferror:
            break
        else:
            print('read: {} ({})'.format(
                o.name, o.name_backwards))

这个版本失败了,因为这里没有可用的 simpleobject 类。

$ python3 pickle_load_from_file_1.py test.dat
traceback (most recent call last):
  file "pickle_load_from_file_1.py", line 15, in 
    o = pickle.load(in_s)
attributeerror: can't get attribute 'simpleobject' on 

下面是正确的版本,它从一开始的脚本中导入了 simpleobject 类。添加导入语句可以让该脚本找到类并构建对象。

from pickle_dump_to_file_1 import simpleobject

现在运行修改后的脚本可以得到预期的结果了。

$ python3 pickle_load_from_file_2.py test.dat
read: pickle (elkcip)
read: preserve (evreserp)
read: last (tsal)

无法序列化的对象

不是所有对象都可以被序列化的。如套接字、文件句柄、数据库连接或其他具有运行时状态的对象,可能依赖于操作系统或其他进程无法有效的存储下来。那些不能被序列化的类可以定义 __getstate__()__setstate__() 方法来返回实例在被序列化时的状态。

 __getstate__() 方法须返回一个包含该对象内部状态的对象。一种便捷的方式是使用字典,字典的值可以是任意可序列化对象。然后状态会被存储,当对象序列化时传递给 __setstate__() 方法。

pickle_state.py

import pickle
class state:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return 'state({!r})'.format(self.__dict__)
class myclass:
    def __init__(self, name):
        print('myclass.__init__({})'.format(name))
        self._set_name(name)
    def _set_name(self, name):
        self.name = name
        self.computed = name[::-1]
    def __repr__(self):
        return 'myclass({!r}) (computed={!r})'.format(
            self.name, self.computed)
    def __getstate__(self):
        state = state(self.name)
        print('__getstate__ -> {!r}'.format(state))
        return state
    def __setstate__(self, state):
        print('__setstate__({!r})'.format(state))
        self._set_name(state.name)
inst = myclass('name here')
print('before:', inst)
dumped = pickle.dumps(inst)
reloaded = pickle.loads(dumped)
print('after:', reloaded)

这个例子使用一个单独的 state 对象存储 myclass 的内部状态。当 myclass 的实例反序列化时,会给  __setstate__() 传入一个 state 的实例去初始化新的对象。

$ python3 pickle_state.py
myclass.__init__(name here)
before: myclass('name here') (computed='ereh eman')
__getstate__ -> state({'name': 'name here'})
__setstate__(state({'name': 'name here'}))
after: myclass('name here') (computed='ereh eman')

警告

如果 __getstate__() 返回值是 false,则 __setstate__() 在对象反序列化时不会被调用。

循环引用

序列化协议会自动处理对象间的循环引用,所以即使复杂的数据结构也不需要去特殊处理。考虑下图,它包含了多个循环,但正确的结构仍然能被反序列化输出。

"a"; "root" -> "b"; "a" -> "b"; "b" -> "a"; "b" -> "c"; "a" -> "a"; }" />

序列化一个循环引用的数据结构

pickle_cycle.py

import pickle
class node:
    """一个简单的有向图
    """
    def __init__(self, name):
        self.name = name
        self.connections = []
    def add_edge(self, node):
         """在这个节点和其他节点间建立一条边 
                 """
        self.connections.append(node)
    def __iter__(self):
        return iter(self.connections)
def preorder_traversal(root, seen=none, parent=none):
    """给一个图生成边的生成器函数
    """
    if seen is none:
        seen = set()
    yield (parent, root)
    if root in seen:
        return
    seen.add(root)
    for node in root:
        recurse = preorder_traversal(node, seen, root)
        for parent, subnode in recurse:
            yield (parent, subnode)
def show_edges(root):
     """打印输出图的所有边
         """
    for parent, child in preorder_traversal(root):
        if not parent:
            continue
        print('{:>5} -> {:>2} ({})'.format(
            parent.name, child.name, id(child)))
# 创建有向图
root = node('root')
a = node('a')
b = node('b')
c = node('c')
# 给节点间添加边
root.add_edge(a)
root.add_edge(b)
a.add_edge(b)
b.add_edge(a)
b.add_edge(c)
a.add_edge(a)
print('original graph:')
show_edges(root)
# 序列化和反序列化有向图
# 产生一组新的节点
dumped = pickle.dumps(root)
reloaded = pickle.loads(dumped)
print('\nreloaded graph:')
show_edges(reloaded)

经过序列化和反序列化,这些新的有向图节点对象并不是一开始创建的那些对象,但对象之间的关系保持不变,这可以通过检查对象 id() 返回的值验证。

$ python3 pickle_cycle.py
original graph:
 root ->  a (4315798272)
    a ->  b (4315798384)
    b ->  a (4315798272)
    b ->  c (4315799112)
    a ->  a (4315798272)
 root ->  b (4315798384)
reloaded graph:
 root ->  a (4315904096)
    a ->  b (4315904152)
    b ->  a (4315904096)
    b ->  c (4315904208)
    a ->  a (4315904096)
 root ->  b (4315904152)

扩展阅读

  •  -- 序列化协议的第4个版本
  •  -- shelve 模块使用 pickle 在 dbm 数据库中存储数据。
  •  -- alexandre vassalotti 编写

本文章首发在 金年会app官方网 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 cc 协议,如果我们的工作有侵犯到您的权益,请及时联系金年会app官方网。

原文地址:https://learnku.com/docs/pymotw/pickle-o...

译文地址:https://learnku.com/docs/pymotw/pickle-o...

上一篇 下一篇
讨论数量: 0



暂无话题~
网站地图