web-dev-qa-db-ja.com

サブクラス化Python辞書を__setitem__をオーバーライドする

dictをサブクラス化し、__setitem__をオーバーライドするクラスを構築しています。私のメソッドは、辞書項目が設定される可能性のあるすべてのインスタンスで呼び出されることを確認したいと思います。

Python(この場合、2.6.4)が値を設定するときにオーバーライドされた__setitem__メソッドを呼び出さず、代わりに直接PyDict_SetItemを呼び出す3つの状況を発見しました

  1. コンストラクタで
  2. setdefaultメソッド
  3. updateメソッド

非常に簡単なテストとして:

class MyDict(dict):
    def __setitem__(self, key, value):
        print "Here"
        super(MyDict, self).__setitem__(key, str(value).upper())

>>> a = MyDict(abc=123)
>>> a['def'] = 234
Here
>>> a.update({'ghi': 345})
>>> a.setdefault('jkl', 456)
456
>>> print a
{'jkl': 456, 'abc': 123, 'ghi': 345, 'def': '234'}

アイテムを明示的に設定した場合にのみ、オーバーライドされたメソッドが呼び出されることがわかります。 Pythonを取得するには、常に__setitem__メソッドを呼び出すために、これらの3つのメソッドを次のように再実装する必要がありました。

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

    def __setitem__(self, key, value):
        print "Here"
        super(MyUpdateDict, self).__setitem__(key, value)

    def update(self, *args, **kwargs):
        if args:
            if len(args) > 1:
                raise TypeError("update expected at most 1 arguments, got %d" % len(args))
            other = dict(args[0])
            for key in other:
                self[key] = other[key]
        for key in kwargs:
            self[key] = kwargs[key]

    def setdefault(self, key, value=None):
        if key not in self:
            self[key] = value
        return self[key]

Pythonはalwaysが私の__setitem__メソッドを呼び出すことを知るために、オーバーライドする必要がある他のメソッドはありますか? ?

[〜#〜]更新[〜#〜]

Gsの提案に従い、次のようにUserDict(実際には、キーを反復処理するため、IterableUserDict)をサブクラス化してみました。

from UserDict import *;
class MyUserDict(IterableUserDict):
    def __init__(self, *args, **kwargs):
        UserDict.__init__(self,*args,**kwargs)

    def __setitem__(self, key, value):
        print "Here"
        UserDict.__setitem__(self,key, value)

このクラスはsetdefault__setitem__を正しく呼び出しているようですが、updateで、または初期データがコンストラクターに提供されている場合は呼び出されません。

更新2

Peter Hansenの提案により、dictobject.cをより注意深く見る必要があり、組み込みのディクショナリーコンストラクターが組み込みの更新メソッドを呼び出すだけなので、更新メソッドが少し簡略化できることに気付きました。これは次のようになります。

def update(self, *args, **kwargs):
    if len(args) > 1:
        raise TypeError("update expected at most 1 arguments, got %d" % len(args))
    other = dict(*args, **kwargs)
    for key in other:
        self[key] = other[key]
44
Ian Clelland

私は自分の質問に答えています。結局、新しいマッピングクラスを作成するのではなく、本当にdo Dictをサブクラス化することを決定したので、UserDictは、場合によっては、基になるDictオブジェクトに委ねます。提供された__setitem__を使用するよりも。

Python 2.6.4ソース(主にObjects/dictobject.cですが、さまざまな方法がどこで使用されているかを確認するために他の場所を探しました))を読んだ後、私の理解は次のとおりです。コードisオブジェクトが変更されるたびに__setitem__を呼び出し、それ以外の場合はPython Dict:

Peter Hansenの提案により、dictobject.cをより注意深く検討するようになり、組み込みディクショナリコンストラクターが組み込みの更新メソッドを呼び出すだけなので、元の回答の更新メソッドが少し簡略化される可能性があることに気付きました。したがって、私の回答の2番目の更新が以下のコードに追加されました(参考になる人が;-)。

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

    def __setitem__(self, key, value):
        # optional processing here
        super(MyUpdateDict, self).__setitem__(key, value)

    def update(self, *args, **kwargs):
        if args:
            if len(args) > 1:
                raise TypeError("update expected at most 1 arguments, "
                                "got %d" % len(args))
            other = dict(args[0])
            for key in other:
                self[key] = other[key]
        for key in kwargs:
            self[key] = kwargs[key]

    def setdefault(self, key, value=None):
        if key not in self:
            self[key] = value
        return self[key]

私はこのコードでそれをテストしました:

def test_updates(dictish):
    dictish['abc'] = 123
    dictish.update({'def': 234})
    dictish.update(red=1, blue=2)
    dictish.update([('orange', 3), ('green',4)])
    dictish.update({'hello': 'kitty'}, black='white')
    dictish.update({'yellow': 5}, yellow=6)
    dictish.setdefault('brown',7)
    dictish.setdefault('pink')
    try:
        dictish.update({'gold': 8}, [('purple', 9)], silver=10)
    except TypeError:
        pass
    else:
        raise RunTimeException("Error did not occur as planned")

python_dict = dict([('b',2),('c',3)],a=1)
test_updates(python_dict)

my_dict = MyUpdateDict([('b',2),('c',3)],a=1)
test_updates(my_dict)

そしてそれは通過します。私が試した他のすべての実装は、ある時点で失敗しました。何かを逃したことを示す答えはすべて受け入れますが、それ以外の場合は、このチェックマークの横にあるチェックマークを数日間チェックして、正しい答えと呼びます:)

50
Ian Clelland

Dictをサブクラス化するためのユースケースは何ですか?

Dictのようなオブジェクトを実装するためにこれを行う必要はありません。通常のクラスを記述してから、dictインターフェースの必要なサブセットのサポートを追加する方が簡単な場合があります。

目的を達成するための最良の方法は、おそらくMutableMapping抽象基本クラスです。 PEP 3119-抽象基本クラスの紹介

これは、「オーバーライドする必要のある他のメソッドはありますか?」という質問に答えるのにも役立ちます。すべての抽象メソッドをオーバーライドする必要があります。 MutableMappingの場合:抽象メソッドには、setitemdelitemが含まれます。具体的なメソッドには、pop、popitem、clear、updateがあります。

4
mluebke

Ianの回答とコメントは非常に役に立ち、明確でした。スーパークラスの__init__メソッドへの最初の呼び出しは、必要でない場合は安全である可能性があることを指摘します。最近、カスタムを実装する必要がありました OrderedDict (私はPython 2.7):提案されたMyUpdateDict実装に従ってコードを実装および変更した後、単純に

class MyUpdateDict(dict):

と:

from collections import OrderedDict
class MyUpdateDict(OrderedDict):

次に、上記のテストコードが失敗しました。

Traceback (most recent call last):
File "Desktop/test_updates.py", line 52, in <module>
    my_dict = MyUpdateDict([('b',2),('c',3)],a=1)
File "Desktop/test_updates.py", line 5, in __init__
    self.update(*args, **kwargs)
File "Desktop/test_updates.py", line 18, in update
    self[key] = other[key]
File "Desktop/test_updates.py", line 9, in __setitem__
    super(MyUpdateDict, self).__setitem__(key, value)
File "/usr/lib/python2.7/collections.py", line 59, in __setitem__
    root = self.__root
AttributeError: 'MyUpdateDict' object has no attribute '_OrderedDict__root'

collections.py code を見ると、OrderedDict__init__メソッドを呼び出す必要がある必要なカスタム属性を初期化してセットアップします。

したがって、最初の呼び出しをsuper __init__メソッドに追加するだけで、

from collections import OrderedDict
class MyUpdateDict(Orderedict):
def __init__(self, *args, **kwargs):
    super(MyUpdateDict, self).__init__() #<-- HERE call to super __init__
    self.update(*args, **kwargs)

dictとOrderedDictの両方で機能する、より一般的なソリューションがあります。

OrderedDictでのみテストしたため、このソリューションが一般的に有効であるかどうかを述べることはできません。ただし、他のdictサブクラスを拡張しようとする場合、super __init__メソッドの呼び出しは無害であるか、有害ではなく必要である可能性があります。

3
rizac

Object ["keyname"] = valueの代わりにobject.keyname = valueを使用します

0
Shahul Hameed P