如何处理`pickle.load`调用`__setitem__`尚未准备好使用呢?

时间:2017-02-18 20:47:32

标签: python python-3.x pickle

我试图实现一个持久字典的(原型,而不是生产)版本,该字典使用磁盘上的pickle作为持久存储。但是,pickle.load为了自己的目的调用__setitem__,并且(当然)重写的方法是为了确保对字典的更改传播回持久存储 - 所以它调用pickle.dump。当然,在打开过程中设置每个项目时,调用pickle.dump是不行的。

有没有办法解决这个问题,除了蛮力(如下)?我尝试使用特殊方法阅读Pickling Class Instances寻找解决方案,但没有找到任何解决方案。

下面的代码监视正在进行的取消修改,并在这种情况下跳过pickle.dump;虽然它工作正常,但感觉很糟糕。

import os, pickle

class PersistentDict(dict):
    def __new__(cls, *args, **kwargs):
        if not args: # when unpickling
            obj = dict.__new__(cls)
            obj.uninitialized = True
            return obj
        path, *args = args
        if os.path.exists(path):
            obj = pickle.load(open(path, 'rb'))
            del obj.uninitialized
            return obj
        else:
            obj = dict.__new__(cls, *args, **kwargs)
            obj.path = path
            obj.dump()
            return obj

    def __init__(self, *args, **kwargs):
        pass

    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        self.dump()

    def __delitem__(self, key):
        super().__delitem__(key)
        self.dump()

    def dump(self):
        if not hasattr(self, 'uninitialized'):
            pickle.dump(self, open(self.path, 'wb'))

    def clear(self):
        os.remove(self.path)

pd = PersistentDict('abc')
assert pd == {}
pd[1] = 2
assert pd == {1: 2}
pd[2] = 4
assert pd == {1: 2, 2: 4}
del pd[1]
assert pd == {2: 4}
xd = PersistentDict('abc')
assert xd == {2: 4}
xd[3] = 6
assert xd == {2: 4, 3: 6}
yd = PersistentDict('abc')
assert yd == {2: 4, 3: 6}
yd.clear()

2 个答案:

答案 0 :(得分:1)

在尝试使用花哨的字典实现时,不建议直接从dict继承。首先,Python的ABI在dict类上采用了一些快捷方式,最终可能会跳过某些调用某些dunder方法 - 而且,正如你可以看到pikcling和unpickling一样 - 字典和它的直接子类将以不同于普通的方式处理对象(其__dict__属性被腌制,而不是用__setitem__设置的键。

首先,从继承collections.UserDict开始 - 这是dict的不同实现,它确保所有数据访问都是通过对dunder特殊方法的正确Python端调用来完成的。您甚至可能希望将其实现为collections.abc.MutableMapping的实现 - 这可确保您必须在代码中实现最少数量的方法,以使您的类像真正的字典一样工作。

第二件事:Pickle协议默认会做“它的事情” - 在映射类中(我没有检查,但显然是),腌制(键,值)对并调用__setitem__每个人都在上瘾。但是,腌制行为是完全可定制的 - 您可以看到on the documentation - 您可以在类上实现明确的__getstate____setstate__方法,以完全控制酸洗/去剔除代码。< / p>

使用MutableMapping并将字典内容存储在关联的内部字典中的示例:

from collections.abc import MutableMapping

class SpecialDict(MutableMapping):
    def __init__(self, path, **kwargs):
        self.path = path
        self.content = dict(**kwargs)
        self.dump()
    def __getitem__(self, key):
        return self.content[key]

    def __setitem__(self, key, value):
        self.content[key] = value
        self.dump()

    def __delitem__(self, key):
        del self.content[key]
        self.dump()

    def __iter__(self):
        return iter(self.content)

    def __len__(self):
        return len(self.content)

    def dump(self):
        ...

    def __getstate__(self):
        return (self.path, self.content)

    def __setstate__(self, state):
        self.path = state[0]
        self.content = state[1]
BTW,使用MutableMapping超类的一大优势是保证如果你正确实现了in the documentation所描述的方法,你的代码就可以生产了(所以,不用担心丢失精致的角落例)。

答案 1 :(得分:0)

Raymond Hettinger在recipe中采用的策略是:

pickle.dump(dict(self), ...)

__init__内部(因此您无需实现__new__

self.update(pickle.load(...))