본질적으로 중첩 된 사전에 해당하는 데이터 구조가 있습니다. 다음과 같이 가정 해 봅시다.
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36}}}
자, 이것을 유지하고 만드는 것은 꽤 고통 스럽습니다. 새로운 주 / 군 / 직업을 가질 때마다 난해한 try / catch 블록을 통해 하위 계층 사전을 만들어야합니다. 또한 모든 값을 다루려면 성가신 중첩 반복자를 만들어야합니다.
튜플을 키로 사용할 수도 있습니다.
{('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}
이렇게하면 값을 매우 간단하고 자연스럽게 반복 할 수 있지만 집계와 같은 작업을 수행하고 사전의 하위 집합을 보는 것이 더 구문 적으로 고통 스럽습니다 (예 : 상태별로 이동하려는 경우).
기본적으로 때로는 중첩 사전을 플랫 사전으로 생각하고 때로는 복잡한 계층 구조로 생각하고 싶습니다. 나는 이것을 모두 한 수업에 넣을 수는 있지만 누군가가 이미 이것을 한 것처럼 보입니다. 또는,이를 위해 정말 우아한 구문 구조가있을 수 있습니다.
어떻게하면 더 잘할 수 있습니까?
부록 : 알고 setdefault()
있지만 실제로 구문을 깨끗하게 만들지는 않습니다. 또한 생성 한 각 하위 사전은 여전히 setdefault()
수동으로 설정해야합니다.
답변
파이썬에서 중첩 사전을 구현하는 가장 좋은 방법은 무엇입니까?
이것은 나쁜 생각입니다.하지 마십시오. 대신 일반 사전을 사용 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
다른 솔루션을 선호 하며 모든 종류의 상황에서 이런 종류의 행동이 필요했습니다.
답변
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}}}
답변
내가이 작은 것을 보지 못했기 때문에 여기에 땀을 들이지 않고 원하는만큼 중첩되는 구술이 있습니다.
# yo dawg, i heard you liked dicts
def yodict():
return defaultdict(yodict)
답변
YAML 파일을 만들고 PyYaml 을 사용하여 읽을 수 있습니다 .
1 단계 : “employment.yml”YAML 파일을 만듭니다.
new jersey:
mercer county:
pumbers: 3
programmers: 81
middlesex county:
salesmen: 62
programmers: 81
new york:
queens county:
plumbers: 9
salesmen: 36
2 단계 : 파이썬으로 읽어보기
import yaml
file_handle = open("employment.yml")
my_shnazzy_dictionary = yaml.safe_load(file_handle)
file_handle.close()
이제 my_shnazzy_dictionary
모든 가치가 있습니다. 이 작업을 즉시 수행해야하는 경우 YAML을 문자열로 생성하여에 입력 할 수 있습니다 yaml.safe_load(...)
.
답변
스타 스키마 디자인이 있으므로 관계형 테이블처럼 구성하고 사전보다는 덜 구성 할 수 있습니다.
import collections
class Jobs( object ):
def __init__( self, state, county, title, count ):
self.state= state
self.count= county
self.title= title
self.count= count
facts = [
Jobs( 'new jersey', 'mercer county', 'plumbers', 3 ),
...
def groupBy( facts, name ):
total= collections.defaultdict( int )
for f in facts:
key= getattr( f, name )
total[key] += f.count
이런 종류의 일은 SQL 오버 헤드없이 데이터웨어 하우스와 같은 디자인을 만드는 데 큰 도움이 될 수 있습니다.
답변
중첩 수준의 수가 적 으면 이것을 사용 collections.defaultdict
합니다.
from collections import defaultdict
def nested_dict_factory():
return defaultdict(int)
def nested_dict_factory2():
return defaultdict(nested_dict_factory)
db = defaultdict(nested_dict_factory2)
db['new jersey']['mercer county']['plumbers'] = 3
db['new jersey']['mercer county']['programmers'] = 81
사용 defaultdict
지저분한 많은 같이 피한다 setdefault()
, get()
등
답변
임의의 깊이로 중첩 된 사전을 반환하는 함수입니다.
from collections import defaultdict
def make_dict():
return defaultdict(make_dict)
다음과 같이 사용하십시오.
d=defaultdict(make_dict)
d["food"]["meat"]="beef"
d["food"]["veggie"]="corn"
d["food"]["sweets"]="ice cream"
d["animal"]["pet"]["dog"]="collie"
d["animal"]["pet"]["cat"]="tabby"
d["animal"]["farm animal"]="chicken"
다음과 같이 모든 것을 반복하십시오.
def iter_all(d,depth=1):
for k,v in d.iteritems():
print "-"*depth,k
if type(v) is defaultdict:
iter_all(v,depth+1)
else:
print "-"*(depth+1),v
iter_all(d)
인쇄됩니다 :
- food
-- sweets
--- ice cream
-- meat
--- beef
-- veggie
--- corn
- animal
-- pet
--- dog
---- labrador
--- cat
---- tabby
-- farm animal
--- chicken
결국 새 항목을 dict에 추가 할 수 없도록 만들 수도 있습니다. 이 모든 것들을 재귀 적 defaultdict
으로 정상으로 쉽게 변환 할 수 dict
있습니다.
def dictify(d):
for k,v in d.iteritems():
if isinstance(v,defaultdict):
d[k] = dictify(v)
return dict(d)