파이썬에서 여러 레벨의 'collection.defaultdict'


176

SO의 훌륭한 사람들 덕분 collections.defaultdict에 가독성과 속도에서 제공하는 가능성을 발견했습니다 . 나는 그것들을 성공으로 사용했다.

이제 세 가지 수준의 사전을 구현하고 싶습니다. 두 가지 최상위 수준 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

나는 어딘가에 두 번째 레벨 defaultdict이 type 이라고 선언해야한다고 생각 int하지만 어디에서 어떻게 할 것인지 찾지 못했습니다.

내가 defaultdict처음 사용하는 이유 는 각각의 새 키에 대한 사전을 초기화하지 않아도되기 때문입니다.

더 우아한 제안이 있습니까?

고마워 pythoneers!

답변:


341

사용하다:

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

defaultdict(int)에서 새 키에 액세스 할 때마다 새로 작성 됩니다 d.


2
문제는 피클 링되지 않는다는 것입니다. 이것을 multiprocessing앞뒤로 보내는 것이 불행합니다.
노아

19
@Noah : 람다 대신 명명 된 모듈 수준 함수를 사용하면 피클됩니다.
interjay

4
@ScienceFriction 도움이 필요하십니까? 때 d[new_key]액세스 할 수 있습니다, 그것은 새로운 생성됩니다 람다를 호출합니다 defaultdict(int). 그리고 d[existing_key][new_key2]액세스되면 새로운 int것이 생성됩니다.
interjay

11
대단해. 결혼 서약을 파이썬으로 매일 갱신하는 것 같습니다.
mVChr

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)에 감사드립니다. 이 접근법은 파이썬과 안전하고 얼마나 안전합니까?
Morlock

2
불행히도이 솔루션은 기본 존재의 가장 중요한 부분을 보존하지 않습니다. 이는 키의 존재에 대해 걱정하지 않고 D [ 'key'] + = 1과 같은 것을 작성하는 힘입니다. 이것이 내가 defaultdict를 사용하는 주요 기능입니다 ... 그러나 동적으로 심화되는 사전도 매우 편리하다고 상상할 수 있습니다.
rschwieb

2
@rschwieb 당신은 add 메소드 를 추가하여 + = 1을 쓸 수있는 힘을 더할 수 있습니다 .
spazm

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}}}

인수가 숫자인지 확인하는 대신 (비 파이썬, amirite!) 기본 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

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.