What is the best way to implement nested dictionaries?

Refresh

November 2018

Views

87.7k time

179

I have a data structure which essentially amounts to a nested dictionary. Let's say it looks like this:

{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

Now, maintaining and creating this is pretty painful; every time I have a new state/county/profession I have to create the lower layer dictionaries via obnoxious try/catch blocks. Moreover, I have to create annoying nested iterators if I want to go over all the values.

I could also use tuples as keys, like such:

{('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}

This makes iterating over the values very simple and natural, but it is more syntactically painful to do things like aggregations and looking at subsets of the dictionary (e.g. if I just want to go state-by-state).

Basically, sometimes I want to think of a nested dictionary as a flat dictionary, and sometimes I want to think of it indeed as a complex hierarchy. I could wrap this all in a class, but it seems like someone might have done this already. Alternatively, it seems like there might be some really elegant syntactical constructions to do this.

How could I do this better?

Addendum: I'm aware of setdefault() but it doesn't really make for clean syntax. Also, each sub-dictionary you create still needs to have setdefault() manually set.

YGA

20 answers

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

Мне нравится идея упаковки это в классе и реализации __getitem__и __setitem__таким образом, чтобы они реализовали простой язык запросов:

>>> d['new jersey/mercer county/plumbers'] = 3
>>> d['new jersey/mercer county/programmers'] = 81
>>> d['new jersey/mercer county/programmers']
81
>>> d['new jersey/mercer country']
<view which implicitly adds 'new jersey/mercer county' to queries/mutations>

Если вы хотите получить фантазии вы можете также осуществить что-то вроде:

>>> d['*/*/programmers']
<view which would contain 'programmers' entries>

но в основном я думаю, что такая вещь будет очень интересно реализовать: D

4

Что же касается «неприятных попытки / улова блоков»:

d = {}
d.setdefault('key',{}).setdefault('inner key',{})['inner inner key'] = 'value'
print d

доходность

{'key': {'inner key': {'inner inner key': 'value'}}}

Вы можете использовать это, чтобы преобразовать из вашего плоского словаря формата в структурированном формате:

fd = {('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}

for (k1,k2,k3), v in fd.iteritems():
    d.setdefault(k1, {}).setdefault(k2, {})[k3] = v
5

collections.defaultdictможет быть отнесено к югу, чтобы сделать вложенную Dict. Затем добавить любые полезные итерационные методы для этого класса.

>>> from collections import defaultdict
>>> class nesteddict(defaultdict):
    def __init__(self):
        defaultdict.__init__(self, nesteddict)
    def walk(self):
        for key, value in self.iteritems():
            if isinstance(value, nesteddict):
                for tup in value.walk():
                    yield (key,) + tup
            else:
                yield key, value


>>> nd = nesteddict()
>>> nd['new jersey']['mercer county']['plumbers'] = 3
>>> nd['new jersey']['mercer county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['salesmen'] = 62
>>> nd['new york']['queens county']['plumbers'] = 9
>>> nd['new york']['queens county']['salesmen'] = 36
>>> for tup in nd.walk():
    print tup


('new jersey', 'mercer county', 'programmers', 81)
('new jersey', 'mercer county', 'plumbers', 3)
('new jersey', 'middlesex county', 'programmers', 81)
('new jersey', 'middlesex county', 'salesmen', 62)
('new york', 'queens county', 'salesmen', 36)
('new york', 'queens county', 'plumbers', 9)
7

Я считаю setdefaultвесьма полезным; Он проверяет , если ключ присутствует , и добавляет его , если нет:

d = {}
d.setdefault('new jersey', {}).setdefault('mercer county', {})['plumbers'] = 3

setdefaultвсегда возвращает соответствующий ключ, так что вы на самом деле обновления значения « d» на месте.

Когда дело доходит до итерации, я уверен, что вы могли бы написать генератор достаточно легко, если один уже не существует в Python:

def iterateStates(d):
    # Let's count up the total number of "plumbers" / "dentists" / etc.
    # across all counties and states
    job_totals = {}

    # I guess this is the annoying nested stuff you were talking about?
    for (state, counties) in d.iteritems():
        for (county, jobs) in counties.iteritems():
            for (job, num) in jobs.iteritems():
                # If job isn't already in job_totals, default it to zero
                job_totals[job] = job_totals.get(job, 0) + num

    # Now return an iterator of (job, number) tuples
    return job_totals.iteritems()

# Display all jobs
for (job, num) in iterateStates(d):
    print "There are %d %s in total" % (job, num)
22

Вы можете создать файл YAML и прочитать его в PyYAML .

Шаг 1: Создайте файл YAML, "employment.yml":

new jersey:
  mercer county:
    pumbers: 3
    programmers: 81
  middlesex county:
    salesmen: 62
    programmers: 81
new york:
  queens county:
    plumbers: 9
    salesmen: 36

Шаг 2: Прочитайте это в Python

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(...).

0

У меня есть подобная вещь происходит. У меня есть много случаев, когда я делаю:

thedict = {}
for item in ('foo', 'bar', 'baz'):
  mydict = thedict.get(item, {})
  mydict = get_value_for(item)
  thedict[item] = mydict

Но происходит много уровней в глубину. Это «.get (пункт, {})», что ключ, как это сделает другой словарь, если есть не один уже. В то же время, я думал о том, чтобы иметь дело с этим лучше. Прямо сейчас, есть много

value = mydict.get('foo', {}).get('bar', {}).get('baz', 0)

Так вместо этого, я сделал:

def dictgetter(thedict, default, *args):
  totalargs = len(args)
  for i,arg in enumerate(args):
    if i+1 == totalargs:
      thedict = thedict.get(arg, default)
    else:
      thedict = thedict.get(arg, {})
  return thedict

Который имеет тот же эффект, если вы:

value = dictgetter(mydict, 0, 'foo', 'bar', 'baz')

Лучше? Я думаю так.

uzi
1

Если ваш набор данные не будут оставаться довольно маленькими, вы можете рассмотреть вопрос об использовании реляционной базы данных. Он будет делать то, что вы хотите: сделать его легко добавить счетчики, выбор подмножества отсчетов, и даже агрегированные отсчеты государством, графством, род занятий, или любой их комбинации.

1
class JobDb(object):
    def __init__(self):
        self.data = []
        self.all = set()
        self.free = []
        self.index1 = {}
        self.index2 = {}
        self.index3 = {}

    def _indices(self,(key1,key2,key3)):
        indices = self.all.copy()
        wild = False
        for index,key in ((self.index1,key1),(self.index2,key2),
                                             (self.index3,key3)):
            if key is not None:
                indices &= index.setdefault(key,set())
            else:
                wild = True
        return indices, wild

    def __getitem__(self,key):
        indices, wild = self._indices(key)
        if wild:
            return dict(self.data[i] for i in indices)
        else:
            values = [self.data[i][-1] for i in indices]
            if values:
                return values[0]

    def __setitem__(self,key,value):
        indices, wild = self._indices(key)
        if indices:
            for i in indices:
                self.data[i] = key,value
        elif wild:
            raise KeyError(k)
        else:
            if self.free:
                index = self.free.pop(0)
                self.data[index] = key,value
            else:
                index = len(self.data)
                self.data.append((key,value))
                self.all.add(index)
            self.index1.setdefault(key[0],set()).add(index)
            self.index2.setdefault(key[1],set()).add(index)
            self.index3.setdefault(key[2],set()).add(index)

    def __delitem__(self,key):
        indices,wild = self._indices(key)
        if not indices:
            raise KeyError
        self.index1[key[0]] -= indices
        self.index2[key[1]] -= indices
        self.index3[key[2]] -= indices
        self.all -= indices
        for i in indices:
            self.data[i] = None
        self.free.extend(indices)

    def __len__(self):
        return len(self.all)

    def __iter__(self):
        for key,value in self.data:
            yield key

Пример:

>>> db = JobDb()
>>> db['new jersey', 'mercer county', 'plumbers'] = 3
>>> db['new jersey', 'mercer county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'salesmen'] = 62
>>> db['new york', 'queens county', 'plumbers'] = 9
>>> db['new york', 'queens county', 'salesmen'] = 36

>>> db['new york', None, None]
{('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

>>> db[None, None, 'plumbers']
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new york', 'queens county', 'plumbers'): 9}

>>> db['new jersey', 'mercer county', None]
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81}

>>> db['new jersey', 'middlesex county', 'programmers']
81

>>>

Edit: Теперь возвращаясь словарей при запросе с дикими картами ( None), а также отдельных значений в противном случае.

28

Просто потому, что я не видел этот маленький, вот ДИКТ, который получает как вложенная, как вам нравится, не пот:

# yo dawg, i heard you liked dicts                                                                      
def yodict():
    return defaultdict(yodict)
1

Вы можете использовать рекурсию в лямбды и defaultdict, нет необходимости определять имена:

a = defaultdict((lambda f: f(f))(lambda g: lambda:defaultdict(g(g))))

Вот пример:

>>> a['new jersey']['mercer county']['plumbers']=3
>>> a['new jersey']['middlesex county']['programmers']=81
>>> a['new jersey']['mercer county']['programmers']=81
>>> a['new jersey']['middlesex county']['salesmen']=62
>>> a
defaultdict(<function __main__.<lambda>>,
        {'new jersey': defaultdict(<function __main__.<lambda>>,
                     {'mercer county': defaultdict(<function __main__.<lambda>>,
                                  {'plumbers': 3, 'programmers': 81}),
                      'middlesex county': defaultdict(<function __main__.<lambda>>,
                                  {'programmers': 81, 'salesmen': 62})})})
9

Это функция, которая возвращает вложенный словарь произвольной глубины:

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

Вы , возможно , в конце концов , хотите сделать так , чтобы новые элементы не могут быть добавлены в Словаре. Это легко рекурсивно конвертировать все эти defaultdictс до нормальных dictс.

def dictify(d):
    for k,v in d.iteritems():
        if isinstance(v,defaultdict):
            d[k] = dictify(v)
    return dict(d)
3

Для удобства Перебора вашего вложенного словаря, почему не просто написать простой генератор?

def each_job(my_dict):
    for state, a in my_dict.items():
        for county, b in a.items():
            for job, value in b.items():
                yield {
                    'state'  : state,
                    'county' : county,
                    'job'    : job,
                    'value'  : value
                }

Итак, если у вас есть compilicated вложенного словаря, итерация над ней становится простым:

for r in each_job(my_dict):
    print "There are %d %s in %s, %s" % (r['value'], r['job'], r['county'], r['state'])

Очевидно, что ваш генератор может дать то, что формат данных полезно для вас.

Почему вы используете попытку поймать блоки для чтения дерева? Это достаточно легко (и, возможно, безопаснее) для запроса, существует ли ключ в Словаре, прежде чем пытаться восстановить его. Функция использования защитных положений может выглядеть следующим образом:

if not my_dict.has_key('new jersey'):
    return False

nj_dict = my_dict['new jersey']
...

Или же, возможно, несколько многословным метод заключается в использовании метода GET:

value = my_dict.get('new jersey', {}).get('middlesex county', {}).get('salesmen', 0)

Но несколько более емким образом, вы можете захотеть взглянуть на использование collections.defaultdict , который является частью стандартной библиотеки , так как питон 2.5.

import collections

def state_struct(): return collections.defaultdict(county_struct)
def county_struct(): return collections.defaultdict(job_struct)
def job_struct(): return 0

my_dict = collections.defaultdict(state_struct)

print my_dict['new jersey']['middlesex county']['salesmen']

Я делаю предположение о значении вашей структуры данных здесь, но это должно быть легко настроить для того, что вы на самом деле хотите сделать.

17

Поскольку у вас есть дизайн звезда-схемы, вы можете структурировать это больше похоже на реляционную таблицу и меньше, как словарь.

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.

13

Если число уровней вложенности мало, я использую 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()и т.д.

6

Как и другие предложили, реляционная база данных может быть более полезным для вас. Вы можете использовать в оперативной памяти базы данных sqlite3 в качестве структуры данных для создания таблиц, а затем запросить их.

import sqlite3

c = sqlite3.Connection(':memory:')
c.execute('CREATE TABLE jobs (state, county, title, count)')

c.executemany('insert into jobs values (?, ?, ?, ?)', [
    ('New Jersey', 'Mercer County',    'Programmers', 81),
    ('New Jersey', 'Mercer County',    'Plumbers',     3),
    ('New Jersey', 'Middlesex County', 'Programmers', 81),
    ('New Jersey', 'Middlesex County', 'Salesmen',    62),
    ('New York',   'Queens County',    'Salesmen',    36),
    ('New York',   'Queens County',    'Plumbers',     9),
])

# some example queries
print list(c.execute('SELECT * FROM jobs WHERE county = "Queens County"'))
print list(c.execute('SELECT SUM(count) FROM jobs WHERE title = "Programmers"'))

Это лишь простой пример. Можно определить отдельные таблицы для стран, графств и названия работы.

5

defaultdict() твой друг!

Для двумерного словаря вы можете сделать:

d = defaultdict(defaultdict)
d[1][2] = 3

Для большего числа измерений вы можете:

d = defaultdict(lambda :defaultdict(defaultdict))
d[1][2][3] = 4
145

Каков наилучший способ реализации вложенных словарей в Python?

Реализовать __missing__на более dictподклассы , чтобы установить и вернуть новый экземпляр.

Этот подход был доступен (и документально) , так как Python 2.5, и (особенно ценно для меня) это довольно печатает как обычный Словаре , вместо уродливого печати на autovivified defaultdict:

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

критика

Критика этого типа контейнера является то, что если пользователь misspells ключа, наш код может не тихо:

>>> 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, и , к сожалению, мы не можем вернуть его , как она установлена) .

Обратите внимание, что это те же семантика как наиболее upvoted ответ, но в половине строк кода - реализация 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 на лету. Это может быстро создать иерархическую структуру дерева так глубоко, как вы можете пойти.

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

Несмотря на то, спрашивающий считает , что это не является чистым, я считаю , что предпочтительнее, чем 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 требует нового экземпляра, используется ли он или нет. Однако, Python (или, по крайней мере, CPython) достаточно умны об обработке неиспользуемые и неиспользуемые новые экземпляры, например, повторно расположение в памяти:

>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)

Автоматически оживляется defaultdict

Это хорошо прорисованное внедрение и использование в сценарии , который вы не проверяющие данных о бы быть полезными в качестве реализации __missing__:

from collections import defaultdict

def vivdict():
    return defaultdict(vivdict)

Но если вам нужно, чтобы проверить ваши данные, результаты авто-оживляются defaultdict заполненных данных таким же образом, выглядят следующим образом:

>>> 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чтобы другие решения, и есть в каждой ситуации , когда я нуждался такого поведения.

0

Я использовал для использования этой функции. его безопасно, быстро, легко ремонтопригодны.

def deep_get(dictionary, keys, default=None):
    return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)

Пример :

>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
...     return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = {'person':{'name':{'first':'John'}}}
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>
3

Вы можете использовать Addict: https://github.com/mewwts/addict

>>> from addict import Dict
>>> my_new_shiny_dict = Dict()
>>> my_new_shiny_dict.a.b.c.d.e = 2
>>> my_new_shiny_dict
{'a': {'b': {'c': {'d': {'e': 2}}}}}