我试图实现一个持久字典的(原型,而不是生产)版本,该字典使用磁盘上的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()
答案 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(...))