2017-02-18 78 views
1

我试图实现一个使用磁盘上的pickle作为持久存储的持久性字典的(原型,而非生产版本)。但是,pickle.load出于其自身目的调用__setitem__,这是(当然)被覆盖以确保将字典更改传播回持久性存储的方法 - 因此它调用pickle.dump。当然,因为在取出期间每个项目都被设置,因此拨打pickle.dump并不好。如何处理调用`__setitem__`的pickle.load`还没有准备好?

有没有什么办法可以解决这个问题,除了通过蛮力(如下)?我试着读Pickling Class Instances寻找一种使用特殊方法的解决方案,但没有找到任何解决方案。

下面的代码监视unpickling是否正在进行,并在这种情况下跳过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

给你的类指定一个字典属性并将数据存储在那里可能比较容易,而不是让你的类从字典继承。然后,你可以腌制存储的字典,而不是你的PersistentDict,分离两层。 – BrenBarn

+0

@BrenBarn这正是我的想法,但我非常偏向于继承的开始,直到我总是用组合替换它。所以这一次,我想继承一下。我知道唯一支持继承的论点是,使用'__getattr__'的自动转发不会转发特殊的方法(比如'__getitem__','__contains__','__eq__'等等),而且转发有点麻烦他们全部手动。但这似乎最终成为继承处理比构图更令人沮丧的另一个例子。 – max

回答

0

尝试直接从dict继承时,试图获得花哨的词典实现。首先,Python的ABI在dict类上使用了一些快捷方式,最终可能会跳过某些调用某些dunder方法的调用 - 而且,正如您可以感觉到plit和unpickling的字典和直接子类将以不同于普通方式的方式处理对象(其中有他们__dict__属性腌制,不设置与__setitem__钥匙

因此,对于一两件事,先从collections.UserDict继承 - 这是一个不同的实施dict这ennsures访问是通过一个适当的Python完成的所有数据你可能甚至想实现它作为collections.abc.MutableMapping的实现 - 这可以确保你必须在你的代码中实现最少数量的方法来让小时类工作,就像它是一个真正的词典一样进制。第二件事:Pickle协议默认会执行“它的事情” - 在映射类中是(我没有选中,但显然是),酸洗(键,值)对,并且为每个对象调用__setitem__那些关于unpicling。但酸洗行为是完全可定制的 - 你可以看到on the documentation - 你可以简单地在你的类上实现显式的__getstate____setstate__方法来完全控制酸洗/取出的代码。使用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] 

顺便说一句,使用MutableMapping超类的一个很大的优势是,它是guarranteed如果实现正确描述的方法即in the documentation,您的代码已准备好生产(所以,不需要担心缺少精致的角落案例)。

相关问题