파이썬에서 중첩 사전을 구현하는 가장 좋은 방법은 무엇입니까?
이것은 나쁜 생각입니다.하지 마십시오. 대신 일반 사전을 사용 dict.setdefault
하고 apropos를 사용하십시오 . 따라서 일반적인 사용법으로 키가 누락되면 예상을 얻습니다 KeyError
. 이 행동을 취해야한다고 주장하는 경우, 발로 자신을 쏘는 방법은 다음과 같습니다.
새 인스턴스를 설정하고 반환하기 __missing__
위해 dict
서브 클래스를 구현하십시오 .
이 접근법은 Python 2.5부터 사용 가능 하고 문서화 되어 있으며 (특히 나에게 귀중한) 자동 활성화 된 defaultdict의 추악한 인쇄 대신 일반 dict처럼 인쇄합니다.
class Vividict(dict):
def __missing__(self, key):
value = self[key] = type(self)() # retain local pointer to value
return value # faster to return than dict lookup
(메모 self[key]
는 과제의 왼쪽에 있으므로 재귀는 없습니다.)
데이터가 있다고 가정 해보십시오.
data = {('new jersey', 'mercer county', 'plumbers'): 3,
('new jersey', 'mercer county', 'programmers'): 81,
('new jersey', 'middlesex county', 'programmers'): 81,
('new jersey', 'middlesex county', 'salesmen'): 62,
('new york', 'queens county', 'plumbers'): 9,
('new york', 'queens county', 'salesmen'): 36}
사용 코드는 다음과 같습니다.
vividict = Vividict()
for (state, county, occupation), number in data.items():
vividict[state][county][occupation] = number
그리고 지금:
>>> import pprint
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36}}}
비판
이 유형의 컨테이너에 대한 비판은 사용자가 키의 철자를 잘못 입력하면 코드가 자동으로 실패 할 수 있다는 것입니다.
>>> vividict['new york']['queens counyt']
{}
또한 이제 데이터에 철자가 틀린 카운티가 있습니다.
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36},
'queens counyt': {}}}
설명:
우리는 Vividict
키에 액세스 할 때마다 클래스의 또 다른 중첩 인스턴스를 제공하고 있습니다. (값 할당을 반환하면 dict에서 getter를 추가로 호출하지 않아도되므로 설정이 완료되면 반환 할 수 없기 때문에 유용합니다.)
이것들은 가장 많이 응답 된 답변과 동일한 의미이지만 코드 라인의 절반에 해당합니다-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
사용법의 데모
다음은이 dict를 사용하여 중첩 된 dict 구조를 즉시 작성하는 방법에 대한 예입니다. 이를 통해 원하는만큼 깊이 계층 적 트리 구조를 신속하게 만들 수 있습니다.
import pprint
class Vividict(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
d = Vividict()
d['foo']['bar']
d['foo']['baz']
d['fizz']['buzz']
d['primary']['secondary']['tertiary']['quaternary']
pprint.pprint(d)
어떤 출력 :
{'fizz': {'buzz': {}},
'foo': {'bar': {}, 'baz': {}},
'primary': {'secondary': {'tertiary': {'quaternary': {}}}}}
마지막 줄에서 알 수 있듯이 수동 검사를 위해 아름답게 인쇄됩니다. 그러나 데이터를 시각적으로 검사 __missing__
하려면 클래스의 새 인스턴스를 키로 설정하고 반환하는 것이 훨씬 나은 솔루션입니다.
대조를위한 다른 대안들 :
dict.setdefault
asker는 이것이 깨끗하지 않다고 생각하지만, Vividict
나 자신 보다 선호한다고 생각합니다 .
d = {} # or dict()
for (state, county, occupation), number in data.items():
d.setdefault(state, {}).setdefault(county, {})[occupation] = number
그리고 지금:
>>> pprint.pprint(d, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36}}}
철자가 틀리면 실패 할 수 있으며 잘못된 정보로 인해 데이터가 복잡해지지 않습니다
>>> d['new york']['queens counyt']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'queens counyt'
또한 setdefault가 루프에서 사용될 때 훌륭하게 작동한다고 생각하며 키에 대해 무엇을 얻을지 모르지만 반복적 인 사용법은 상당히 부담이되며 사람은 다음을 유지하고 싶지는 않습니다.
d = dict()
d.setdefault('foo', {}).setdefault('bar', {})
d.setdefault('foo', {}).setdefault('baz', {})
d.setdefault('fizz', {}).setdefault('buzz', {})
d.setdefault('primary', {}).setdefault('secondary', {}).setdefault('tertiary', {}).setdefault('quaternary', {})
또 다른 비판은 setdefault가 사용 여부에 관계없이 새 인스턴스를 필요로한다는 것입니다. 그러나 파이썬 (또는 적어도 CPython)은 사용되지 않고 참조되지 않은 새로운 인스턴스를 처리하는 데 다소 똑똑합니다. 예를 들어 메모리의 위치를 재사용합니다.
>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)
자동 활성화 된 defaultdict
이것은 깔끔하게 보이는 구현이며 데이터를 검사하지 않는 스크립트의 사용법은 구현하는 것만 큼 유용합니다 __missing__
.
from collections import defaultdict
def vivdict():
return defaultdict(vivdict)
그러나 데이터를 검사해야하는 경우 동일한 방식으로 데이터로 채워진 자동 활성화 된 기본 결과는 다음과 같습니다.
>>> d = vivdict(); d['foo']['bar']; d['foo']['baz']; d['fizz']['buzz']; d['primary']['secondary']['tertiary']['quaternary']; import pprint;
>>> pprint.pprint(d)
defaultdict(<function vivdict at 0x17B01870>, {'foo': defaultdict(<function vivdict
at 0x17B01870>, {'baz': defaultdict(<function vivdict at 0x17B01870>, {}), 'bar':
defaultdict(<function vivdict at 0x17B01870>, {})}), 'primary': defaultdict(<function
vivdict at 0x17B01870>, {'secondary': defaultdict(<function vivdict at 0x17B01870>,
{'tertiary': defaultdict(<function vivdict at 0x17B01870>, {'quaternary': defaultdict(
<function vivdict at 0x17B01870>, {})})})}), 'fizz': defaultdict(<function vivdict at
0x17B01870>, {'buzz': defaultdict(<function vivdict at 0x17B01870>, {})})})
이 출력은 매우 우아하지 않으며 결과를 읽을 수 없습니다. 일반적으로 제공되는 솔루션은 수동 검사를 위해 재귀 적으로 사전으로 변환하는 것입니다. 이 사소한 해결책은 독자를위한 연습으로 남습니다.
공연
마지막으로 성능을 살펴 보겠습니다. 인스턴스화 비용을 빼고 있습니다.
>>> import timeit
>>> min(timeit.repeat(lambda: {}.setdefault('foo', {}))) - min(timeit.repeat(lambda: {}))
0.13612580299377441
>>> min(timeit.repeat(lambda: vivdict()['foo'])) - min(timeit.repeat(lambda: vivdict()))
0.2936999797821045
>>> min(timeit.repeat(lambda: Vividict()['foo'])) - min(timeit.repeat(lambda: Vividict()))
0.5354437828063965
>>> min(timeit.repeat(lambda: AutoVivification()['foo'])) - min(timeit.repeat(lambda: AutoVivification()))
2.138362169265747
성능에 dict.setdefault
따라 가장 잘 작동합니다. 실행 속도에 관심이있는 경우 프로덕션 코드로 사용하는 것이 좋습니다.
IPython 노트북에서 대화 형 사용을 위해 이것이 필요한 경우 성능은 실제로 중요하지 않습니다.이 경우 출력의 가독성을 위해 Vividict와 함께 할 것입니다. AutoVivification 객체 ( 이 목적으로 만들어진 __getitem__
대신 대신 사용 __missing__
)에 비해 훨씬 뛰어납니다.
결론
새 인스턴스를 설정하고 반환하기 __missing__
위해 서브 클래스 dict
에 구현 하는 것은 대안보다 약간 어렵지만 다음과 같은 이점이 있습니다.
- 쉬운 인스턴스화
- 쉬운 데이터 수집
- 쉬운 데이터보기
수정하는 것보다 덜 복잡하고 성능이 좋기 때문에 __getitem__
해당 방법보다 선호되어야합니다.
그럼에도 불구하고 단점이 있습니다.
- 잘못된 조회는 자동으로 실패합니다.
- 잘못된 조회는 사전에 남아 있습니다.
따라서 나는 개인적으로 setdefault
다른 솔루션을 선호 하며 모든 종류의 상황에서 이런 종류의 행동이 필요했습니다.
Vividict
있습니까? 예3
와list
함께 채울 수 있었다리스트의 DICT의 DICT의 DICT에 대한d['primary']['secondary']['tertiary'].append(element)
. 각 깊이에 대해 3 개의 서로 다른 클래스를 정의 할 수 있지만 더 깨끗한 솔루션을 찾고 싶습니다.