2017-02-18 33 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,您的代碼已準備好生產(所以,不需要擔心缺少精緻的角落案例)。

相關問題