dictを適切にサブクラス化し、__ getitem __&__ setitem__をオーバーライドする方法


84

私はいくつかのコードをデバッグしていて、特定の辞書がいつアクセスされるかを知りたいです。まあ、それは実際にはサブクラスでdictあり、いくつかの追加機能を実装するクラスです。とにかく、私がやりたいのは、dict自分自身をサブクラス化し、オーバーライド__getitem__を追加__setitem__して、デバッグ出力を生成することです。今、私は持っています

class DictWatch(dict):
    def __init__(self, *args):
        dict.__init__(self, args)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        log.info("GET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        return val

    def __setitem__(self, key, val):
        log.info("SET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        dict.__setitem__(self, key, val)

'name_label'は、出力を識別するために使用したい最終的に設定されるキーです。次に、インストルメントしているクラスをDictWatch代わりにサブクラスにdict変更し、スーパーコンストラクターへの呼び出しを変更しました。それでも、何も起こっていないようです。頭がいいと思っていたのですが、別の方向に進むべきかと思いました。

助けてくれてありがとう!


ログの代わりに印刷を使用しようとしましたか?また、ログをどのように作成/構成するのか説明していただけますか?
pajton 2010年

2
dict.__init__かかりません*argsか?
トムラッセル

4
デコレータの良い候補のように見えます。
トムラッセル

回答:


39

あなたがしていることは絶対にうまくいくはずです。クラスをテストしましたが、ログステートメントに開始括弧がないことを除けば、問題なく動作します。私が考えることができるのは2つだけです。まず、ログステートメントの出力が正しく設定されていますか?logging.basicConfig(level=logging.DEBUG)スクリプトの先頭にを配置する必要がある場合があります。

第二に、__getitem__そして__setitem__唯一の中に呼び出され[]たアクセス。したがって、とではなく、をDictWatch介してのみアクセスするようにしてくださいd[key]d.get()d.set()


実際には、余分な(str(dict.get(self, 'name_label')), str(key), str(val)))
パレンで

3
本当。OPへ:将来の参照のために、Python文字列フォーマット演算子の代わりにlog.info( '%s%s%s'、a、b、c)を実行できます。
BrainCore 2010年

ロギングレベルが問題になってしまいました。私は他の誰かのコードをデバッグしていて、元々は別のレベルのデバッグセットの先頭にある別のファイルでテストしていました。ありがとう!
Michael Mior 2010年

73

サブクラス化の際のもう1つの問題dictは、ビルトインがを__init__呼び出さずupdate、ビルトインupdateがを呼び出さないこと__setitem__です。したがって、すべてのsetitem操作で__setitem__関数を実行する場合は、それが自分で呼び出されることを確認する必要があります。

class DictWatch(dict):
    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        print 'GET', key
        return val

    def __setitem__(self, key, val):
        print 'SET', key, val
        dict.__setitem__(self, key, val)

    def __repr__(self):
        dictrepr = dict.__repr__(self)
        return '%s(%s)' % (type(self).__name__, dictrepr)

    def update(self, *args, **kwargs):
        print 'update', args, kwargs
        for k, v in dict(*args, **kwargs).iteritems():
            self[k] = v

9
Python 3を使用している場合printは、この例を変更して、それがprint()関数であり、update()メソッドがのitems()代わりに使用するようにする必要がありiteritems()ます。
アルスワイガート2017

私はあなたのsolを試しましたが、それは1つのレベルのインデックス作成
Andrew Naguib

d [key1]は何か、おそらく辞書を返します。2番目のキーはそれをインデックスします。この手法は、返されたものが時計の動作もサポートしない限り機能しません。
マットアンダーソン

1
@AndrewNaguib:ネストされた配列で機能する必要があるのはなぜですか?ネストされた配列は、通常のpython dictでも機能しません(自分で実装しなかった場合)
IgorChubin19年

1
@AndrewNaguibは:__getitem__テストする必要があるだろうvalし、条件付きでのみそれを行う-すなわちif isinstance(val, dict): ...
マーティ

14

サブクラス化UserDictまたはを検討してくださいUserList。これらのクラスはサブクラス化されることを目的としていますが、通常のクラスdictとそうでlistはなく、最適化が含まれています。


9
参考までに、Python 3.6のドキュメントには、「このクラスの必要性は、dictから直接サブクラス化する機能に部分的に取って代わられました。ただし、基になる辞書は属性としてアクセスできるため、このクラスの操作は簡単です」と記載されています。
ショーン

@andrewの例が役立つかもしれません。
Vasantha GaneshK19年

2
@VasanthaGaneshK treyhunner.com/2019/04/...
SirDorius

9

これで結果が実際に変わることはありません(適切なロギングしきい値の場合は機能するはずです):initは次のようになります:

def __init__(self,*args,**kwargs) : dict.__init__(self,*args,**kwargs) 

代わりに、DictWatch([(1,2)、(2,3)])またはDictWatch(a = 1、b = 2)を使用してメソッドを呼び出すと、失敗するためです。

(または、このためのコンストラクターを定義しないでください)


dict[key]アクセスの形態だけが気になるので、問題ありません。
Michael Mior 2010年

1

あなたがしなければならないのは

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

私の個人的な使用のためのサンプルの使用法

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

    def __setitem__(self, key, item):
        if (isinstance(key, tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a tuple (database_name, table_name) "
                "and value should be iterable")

:python3でのみテスト済み


0

アンドリュー・パテ答えを完了するために、ここでの違いを示す例であるdictとしますUserDict

dictを正しく上書きするのは難しいです:

class MyDict(dict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Bad! MyDict.__setitem__ not called
d.update(c=3)  # Bad! MyDict.__setitem__ not called
d['d'] = 4  # Good!
print(d)  # {'a': 1, 'b': 2, 'c': 3, 'd': 40}

UserDictから継承するcollections.abc.MutableMappingため、カスタマイズがはるかに簡単です。

class MyDict(collections.UserDict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Good: MyDict.__setitem__ correctly called
d.update(c=3)  # Good: MyDict.__setitem__ correctly called
d['d'] = 4  # Good
print(d)  # {'a': 10, 'b': 20, 'c': 30, 'd': 40}

同様に、あなただけ実装する必要が__getitem__自動的に対応するようにkey in my_dictmy_dict.get...

注:UserDictはのサブクラスではないdictため、isinstance(UserDict(), dict)失敗します(ただしisinstance(UserDict(), collections.abc.MutableMapping)機能します)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.