Python的神奇方法指南:Pickling 你的对象

假如你花时间和其他 Pythonistas 打交道,那么你至少有可能听到过 Pickling 这个词。 Pickling 是一种对 Python 数据结构的序列化过程。 如果你需要存储一个对象,之后再取回它(通常是为了缓存)那么它就显得格外地有用了。 同时,它也是产生忧虑和困惑的主要来源。

Pickling 是那么地重要以至于它不仅有自己专属的模块(pickle),还有自己的 protocol 和神奇方法与其相伴。 但首先用简要的文字来解释下如何 pickle 已经存在的类型(如果你已经懂了可以随意跳过这部分内容)

Pickling:盐水中的快速浸泡

让我们跳入 pickling。 话说你有一个词典你想要保存它并在稍后取回。 你可以把它的内容写到一个文件中去,需要非常小心地确保你写了正确的语法,然后用 exec() 或处理文件的输入取回写入的内容。 但这是不稳定的:如果你你在纯文本中保存重要的数据,它有可能被几种方法改变,导致你的程序 crash 或在你的计算机上运行了恶意代码而出错。 于是,我们准备 pickle 它:

import pickle

data = {'foo': [1,2,3],
        'bar': ('Hello','world!'),
        'baz': True}
jar = open('data.pk1', 'wb')
pickle.dump(data, jar) # 把pickled数据写入jar文件
jar.close()

好了现在,已经过去了几个小时。 我们希望拿回数据,而我们需要做的事仅仅是 unpickle 它:

import pickle

pk1_file = open('data.pk1','rb') #连接pickled数据
data = pickle.load(pk1_file) #把数据load到一个变量中去
print data
pk1_file.close()

发生了什么事?正如你的预期,我们获得了 data。

现在,我要给你一些忠告:pickling 并非完美。 Pickle 文件很容易因意外或出于故意行为而被损毁。 Pickling 可能比起使用纯文本文件安全些,但它仍旧有可能会被用来跑恶意代码。 还有因为 Python 版本的不兼容问题,所以不要期望发布 Pickled 对象,也不要期望人们能够打开它们。 但是,它依然是一个强大的缓存工具和其他常见序列化任务。

Pickling你自定义的对象

Pickling 不仅可用在内建类型上,还可以用于遵守 pickle 协议的任何类。 pickle 协议有 4 个可选方法用于定制 Python 对象如何运行(这跟 C 扩展有点不同,但那不在我们讨论的范围内):

__getinitargs__(self)

如果你想当你的类 unpickled 时调用 __init__,那你可以定义__getinitargs__,该方法应该返回一个元组的参数,然后你可以把他传递给 __init__。注意,该方法仅适用于旧式类。

__getnewargs__(self)

对于新式类,你可以影响有哪些参数会被传递到 __new__ 进行 unpickling。 该方法同样应该返回一个元组参数,然后能传递给 __new__

__getstate__(self)

代替对象的 __dict__ 属性被保存。 当对象 pickled,你可返回一个自定义的状态被保存。 当对象 unpickled 时,这个状态将会被 __setstate__ 使用。

__setstate__(self, state)

对象 unpickled 时,如果 __setstate__ 定义对象状态会传递来代替直接用对象的 __dict__ 属性。 这正好跟__getstate__ 手牵手:当二者都被定义了,你可以描述对象的 pickled 状态,任何你想要的。

一个例子:

我们的例子是 Slate 类,它会记忆它曾经的值和已经写入的值。 然而,当这特殊的 slate 每一次 pickle 都会被清空:当前值不会被保存。

import time

class Slate:
    '''存储一个字符串和一个变更log,当Pickle时会忘记它的值'''

    def __init__(self, value):
        self.value = value
        self.last_change = time.asctime()
        self.history = {}

    def change(self, new_value):
        # 改变值,提交最后的值到历史记录
        self.history[self.last_change] = self.value
        self.value = new_value
        self.last_change = time.asctime()

    def print_changes(self):
        print 'Changelog for Slate object:'
        for k, v in self.history.items():
            print '%st %s' % (k, v)

    def __getstate__(self):
        # 故意不返回self.value 或 self.last_change.
        # 当unpickle,我们希望有一块空白的"slate"
        return self.history

    def __setstate__(self, state):
        # 让 self.history = state 和 last_change 和 value被定义
        self.history = state
        self.value, self.last_change = None, None
相关的文章:

暂无评论

写评论