2017-02-18 3 views
1

私は、ディスク上のpickleを永続ストレージとして使用する永続的な辞書の(プロダクトではなく、プロトタイプではなく)バージョンを実装しようとしていました。しかし、pickle.loadはそれ自身の目的で__setitem__を呼び出します。これは、辞書への変更が永続的な記憶域に確実に反映されるように(もちろん)オーバーライドされているメソッドです - したがって、pickle.dumpが呼び出されます。もちろん、pickle.dumpに電話をかけることはできません。準備が整っていない `pickle.load`を呼び出す` __setitem__`をどう扱うか?

これを解決する方法はありますか?ブルートフォース以外の方法はありますか?私はPickling Class Instancesを読んで、特殊な方法を使って解決策を探しましたが、何も見つかりませんでした。

以下のコードは、unpicklingが進行中かどうかを監視し、その場合はpickle.dumpをスキップします。それはうまく動作しますが、それはハッキリと感じます。派手な辞書の実装を取得しようとしたときdictから直接継承

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

クラスをdictから継承するのではなく、クラスに属性を与え、そこにデータを格納する方が簡単かもしれません。次に、PersistentDictの代わりに格納されたdictをpickleして、2つのレイヤーを分離することができます。 – BrenBarn

+0

@BrenBarnそれはまさに私が考えていたものですが、私はいつもそれを構成に置き換えるという点で、まずは継承に対して非常に偏っています。今回は、継承を試してみたかったのです。私が知っている継承に賛成する唯一の議論は、 '__getattr__'を使った自動転送は特別なメソッド(' __getitem__'、 '__contains__'、' __eq__'など)を転送しないということです。転送する手間が少しありますそれらはすべて手動で行います。しかし、これは継承が構成よりも面倒な別の例として終わるようです。 – max

答えて

0

はお勧めできません。まず、PythonのABIはdictクラスのいくつかのショートカットをとり、最終的に特定のdunderメソッドを呼び出すことができなくなります。また、piclingとunpicklingのときに認識できるように、辞書や直接サブクラスは通常とは異なる方法で扱われます。漬け彼ら__dict__属性を持つオブジェクト(、ない__setitem__で設定し、そのキー

ので、一つには、collections.UserDictから継承で始まる - これは、すべてのデータアクセスが適切なのPythonを介して行われennsures dictの異なる実装ですダンダーの特別なメソッドへのサイドコールを呼び出すことができます。collections.abc.MutableMappingの実装として実装したいと思うかもしれません - 実際のディクティオのように時間クラスを動作させるためには、 nary。

2番目のこと:Pickleプロトコルはデフォルトで "そのこと"を行います - マッピングクラスでは(私はチェックしていませんが、明らかに)、(キー、値)のペアをpicklingし、それぞれ__setitem__それらはペインティングしています。しかし、酸洗いの振る舞いは完全にカスタマイズ可能です。on the documentationが表示されていますので、あなたのクラスにexplict __getstate____setstate__メソッドを実装して、酸洗い/コード解除コードを完全に制御することができます。ところで、MutableMappingスーパークラスを使用することの大きな利点は、あなたが適切に実装する場合の方法が記載されているguarranteedされていることである

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を使用して、関連する内部辞書で辞書コンテンツを格納

in the documentation、あなたのコードは生産準備が整っています(絶妙なコーナーケースがないことを心配する必要はありません)。

関連する問題