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

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

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

Ответ 1

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

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

Этот подход был доступен (и документирован) с Python 2.5 и (особенно ценный для меня), он довольно печатает точно так же, как обычный dict, вместо уродливой печати автоподтвержденного 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}}}

критика

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

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

Обратите внимание, что это те же семантики, что и самый верхний ответ, но в половине строк кода - реализация nosklo:

class AutoVivification(dict):
    """Implementation of perl 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__ данные, было бы так же полезно, как реализация __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>, {})})})

Этот результат довольно неэффективен, и результаты довольно нечитаемы. Обычно задается рекурсивное преобразование обратно в dict для ручного осмотра. Это нетривиальное решение остается в качестве упражнения для читателя.

Представление

Наконец, давайте посмотрим на производительность. Я вычитаю затраты на создание экземпляра.

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

Ответ 2

class AutoVivification(dict):
    """Implementation of perl 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}}}

Ответ 3

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

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

Ответ 4

Вы можете создать файл 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(...).

Ответ 5

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

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.

Ответ 6

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

Ответ 7

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

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 s.

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

Ответ 8

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

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

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

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

def iterateStates(d):
    # Let 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)

Ответ 9

Как и другие, реляционная база данных может быть более полезной для вас. Вы можете использовать базу данных 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"'))

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

Ответ 10

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)

Ответ 11

defaultdict() - ваш друг!

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

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

Для большего размера вы можете:

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

Ответ 12

Что касается "неприятных блоков try/catch":

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

Ответ 13

Вы можете использовать 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}}}}}

Ответ 14

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

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
                }

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

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

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

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

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, который является частью стандартной библиотеки поскольку python 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']

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

Ответ 15

Мне нравится идея обернуть это в классе и реализовать __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

Ответ 16

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

>>>

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

Ответ 17

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

Ответ 18

Вы можете использовать рекурсию в lambdas и 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})})})

Ответ 19

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

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

Ответ 20

У меня тоже похоже. У меня много случаев, когда я делаю:

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

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

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')

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