Pythonの複数レベルの「collection.defaultdict」


176

SOの優れた人々のおかげで、私はcollections.defaultdict、特に可読性とスピードの可能性を発見しました。私はそれらを成功させて使用しました。

ここで、3つのレベルの辞書を実装したいと思います。上位2つの辞書defaultdictと下位の辞書intです。これを行う適切な方法が見つかりません。これが私の試みです:

from collections import defaultdict
d = defaultdict(defaultdict)
a = [("key1", {"a1":22, "a2":33}),
     ("key2", {"a1":32, "a2":55}),
     ("key3", {"a1":43, "a2":44})]
for i in a:
    d[i[0]] = i[1]

これで機能しますが、望ましい動作である以下は機能しません。

d["key4"]["a1"] + 1

私はどこかで2番目のレベルdefaultdictがtype intであることを宣言するべきだったのではないかと思いますが、どこでどのように行うのかわかりませんでした。

私がdefaultdict最初に使用している理由は、新しいキーごとに辞書を初期化する必要がないようにするためです。

よりエレガントな提案はありますか?

pythoneersに感謝!

回答:


341

使用する:

from collections import defaultdict
d = defaultdict(lambda: defaultdict(int))

これによりdefaultdict(int)、で新しいキーにアクセスするたびに、新しいが作成されdます。


2
問題はピクルスにならないことだけです。つまり、multiprocessingこれらをやり取りすることに不満があります。
Noah

19
@Noah:ラムダの代わりに名前付きのモジュールレベルの関数を使用すると、ピクルスになります。
interjay 2012年

4
@ScienceFriction何か支援が必要なことはありますか?にd[new_key]アクセスすると、新しいを作成するラムダが呼び出されますdefaultdict(int)。にd[existing_key][new_key2]アクセスすると、新しいものintが作成されます。
interjay 2013年

11
これは素晴らしいです。Pythonへの夫婦の誓いを毎日更新しているようです。
mVChr 2014年

3
このメソッドを一緒に使用する方法の詳細multiprocessingと、名前付きのモジュールレベルの関数とは何ですか?この質問は続きます。
セシリア

32

ピクルス可能なネストされたdefaultdictを作成する別の方法は、ラムダの代わりに部分オブジェクトを使用することです。

from functools import partial
...
d = defaultdict(partial(defaultdict, int))

defaultdictクラスはモジュールレベルでグローバルにアクセスできるため、これは機能します。

"ラップする関数(この場合はクラス)がグローバルにアクセス可能でない限り、部分オブジェクトをピクルすることはできません... __name__(__module__内)の下で- ラップされた部分関数のピクル


12

より一般的な解決策については、noskloの回答をここで見てください。

class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

テスト:

a = AutoVivification()

a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6

print a

出力:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}

リンク@ miles82(および編集、@ voyager)をありがとう。このアプローチはpythonesqueで安全ですか?
モーロック2010

2
残念ながら、このソリューションはdefaultdictの最も便利な部分を保持しません。これは、キーの存在を気にせずにD ['key'] + = 1のようなものを書く力です。これが私がdefaultdictを使用する主な機能です...しかし、動的に深める辞書もかなり便利だと想像できます。
rschwieb

2
@rschwiebには、addメソッドを追加することで書き込みパワーを追加できます+ = 1
けいれん

5

@rschwiebののリクエストに従って、メソッドを定義することで追加をオーバーライドすることによりD['key'] += 1以前のように拡張し__add__て、これをcollections.Counter()

最初__missing__にが呼び出され、新しい空の値が作成され__add__ます。これはに渡されます。空の値がであることを考慮して、値をテストしますFalse

オーバーライドの詳細については、数値型のエミュレーションを参照してください。

from numbers import Number


class autovivify(dict):
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

    def __add__(self, x):
        """ override addition for numeric types when self is empty """
        if not self and isinstance(x, Number):
            return x
        raise ValueError

    def __sub__(self, x):
        if not self and isinstance(x, Number):
            return -1 * x
        raise ValueError

例:

>>> import autovivify
>>> a = autovivify.autovivify()
>>> a
{}
>>> a[2]
{}
>>> a
{2: {}}
>>> a[4] += 1
>>> a[5][3][2] -= 1
>>> a
{2: {}, 4: 1, 5: {3: {2: -1}}}

引数をチェックするのではなく、数値(非常に非Python、アミライト!)で、デフォルトの0値を指定して、操作を試すことができます。

class av2(dict):
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

    def __add__(self, x):
        """ override addition when self is empty """
        if not self:
            return 0 + x
        raise ValueError

    def __sub__(self, x):
        """ override subtraction when self is empty """
        if not self:
            return 0 - x
        raise ValueError

これらはValueErrorではなくNotImplementedを発生させるべきですか?
spazm 2014

5

パーティーに遅れましたが、恣意的な深さのために私は自分が次のようなことをしているのを見つけました:

from collections import defaultdict

class DeepDict(defaultdict):
    def __call__(self):
        return DeepDict(self.default_factory)

ここでの秘訣は、基本的にDeepDictインスタンス自体を欠損値を構築するための有効なファクトリにすることです。今、私たちは次のようなことができます

dd = DeepDict(DeepDict(list))
dd[1][2].extend([3,4])
sum(dd[1][2])  # 7

ddd = DeepDict(DeepDict(DeepDict(list)))
ddd[1][2][3].extend([4,5])
sum(ddd[1][2][3])  # 9

1
def _sub_getitem(self, k):
    try:
        # sub.__class__.__bases__[0]
        real_val = self.__class__.mro()[-2].__getitem__(self, k)
        val = '' if real_val is None else real_val
    except Exception:
        val = ''
        real_val = None
    # isinstance(Avoid,dict)也是true,会一直递归死
    if type(val) in (dict, list, str, tuple):
        val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
        # 重新赋值当前字典键为返回值,当对其赋值时可回溯
        if all([real_val is not None, isinstance(self, (dict, list)), type(k) is not slice]):
            self[k] = val
    return val


def _sub_pop(self, k=-1):
    try:
        val = self.__class__.mro()[-2].pop(self, k)
        val = '' if val is None else val
    except Exception:
        val = ''
    if type(val) in (dict, list, str, tuple):
        val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
    return val


class DefaultDict(dict):
    def __getitem__(self, k):
        return _sub_getitem(self, k)

    def pop(self, k):
        return _sub_pop(self, k)

In[8]: d=DefaultDict()
In[9]: d['a']['b']['c']['d']
Out[9]: ''
In[10]: d['a']="ggggggg"
In[11]: d['a']
Out[11]: 'ggggggg'
In[12]: d['a']['pp']
Out[12]: ''

再びエラーはありません。ネストされたレベルの数に関係なく。エラーもポップしません

dd = DefaultDict({"1":333333})

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