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

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

{'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()установлен вручную.

20 ОТВЕТОВ
РЕШЕНИЕ

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

Это плохая идея, не делай этого. Вместо этого используйте обычный словарь и используйте dict.setdefaultгде, кстати, поэтому, когда ключи отсутствуют при обычном использовании, вы получите ожидаемый KeyError. Если вы настаиваете на том, чтобы получить такое поведение, вот как застрелить себя в ногу:

Реализация __missing__на dictподклассе для установки и возврата нового экземпляра.

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

критика

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

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

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

Хотя спрашивающий думает, что это не чисто, я считаю, что это предпочтительнее, чем 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)

Авто-оживленный дефолтный приговор

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

173
12.12.2019 00:27:59
Отличный ответ! Есть ли способ указать конечную глубину и тип листа для Vividict? Например, 3и listдля диктата диктата списков, которые могут быть заполнены d['primary']['secondary']['tertiary'].append(element). Я мог бы определить 3 разных класса для каждой глубины, но я бы хотел найти более чистое решение.
Eric Duminil 27.12.2017 17:22:47
@EricDuminil d['primary']['secondary'].setdefault('tertiary', []).append('element')- ?? Спасибо за комплимент, но позвольте мне быть честным - я никогда не использую __missing__- я всегда использую setdefault. Я, вероятно, должен обновить свое заключение / вступление ...
Aaron Hall♦ 27.12.2017 18:57:00
@AaronHall Правильное поведение - код должен создавать диктовку, если это необходимо. В этом случае путем переопределения предыдущего присвоенного значения.
nehem 13.03.2019 01:18:32
@AaronHall Также вы можете помочь мне понять, что имеется в виду, The bad lookup will remain in the dictionary.когда я рассматриваю возможность использования этого решения ?. Очень признателен. Thx
nehem 13.03.2019 02:56:05
@AaronHall Проблема с ним не удалась бы, setdefaultесли бы он вложил более двух уровней глубины. Похоже, что ни одна структура в Python не может предложить истинное оживление, как описано. Мне пришлось согласиться на два метода заявлений, один для get_nested&, для set_nestedкоторого принимают ссылку на dict и список вложенных атрибутов.
nehem 13.03.2019 04:56:55

Я нахожу 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)
7
6.07.2012 21:47:22
Мне нравится это решение, но когда я пытаюсь: count.setdefault (a, {}). Setdefault (b, {}). Setdefault (c, 0) + = 1 я получаю «недопустимое выражение для расширенного назначения»
dfrankow 8.03.2011 19:31:53

Что касается "неприятных блоков 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
4
11.03.2009 17:30:56

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

2
6.07.2012 21:48:21
Я думаю, что это плохая идея - вы никогда не сможете предсказать синтаксис ключей. Вы все равно переопределяете getitem и setitem, но пусть они принимают кортежи.
YGA 11.03.2009 17:38:58
@YGA Вы, вероятно, правы, но интересно подумать о реализации таких мини-языков.
Aaron Maenpaa 11.03.2009 18:25:15

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

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.

18
11.03.2009 17:29:12
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) и одиночными значениями в противном случае.

1
11.03.2009 21:44:27
Зачем возвращать списки? Кажется, он должен либо возвращать словарь (чтобы вы знали, что представляет каждое число), либо сумму (поскольку это все, что вы действительно можете сделать со списком).
Ben Blank 11.03.2009 20:52:50

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

14
6.07.2012 21:45:25
+1: defaultdict - одно из моих самых любимых дополнений к python. Нет больше .setdefault ()!
John Fouhy 11.03.2009 23:13:02

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

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 try для чтения дерева? Достаточно легко (и, вероятно, безопаснее) запросить, существует ли ключ в dict, прежде чем пытаться получить его. Функция, использующая предложения guard, может выглядеть так:

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)

Но для более краткого подхода вы можете рассмотреть использование collection.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']

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

3
11.03.2009 20:38:52

Вы можете создать файл YAML и прочитать его, используя PyYaml .

Шаг 1: Создайте файл YAML, "jobs.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(...).

24
6.07.2012 21:42:20
Я определенно выбрал YAML для ввода большого количества глубоко вложенных данных (и файлов конфигурации, макетов данных и т. Д.). Если OP не хочет, чтобы дополнительные файлы лежали без дела, просто используйте обычную строку Python в каком-то файле и проанализируйте ее с помощью YAML.
kmelvn 11.03.2009 20:49:11
Хороший момент при создании строк YAML: это был бы гораздо более чистый подход, чем повторное использование модуля «tempfile».
Pete 11.03.2009 20:52:02

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

1
11.03.2009 20:30:20

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

>>> 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)
5
6.07.2012 21:43:06
Этот ответ наиболее близок к тому, что я искал. Но в идеале должны быть всевозможные вспомогательные функции, например, walk_keys () или тому подобное. Я удивлен, что в стандартных библиотеках ничего нет для этого.
YGA 14.03.2009 05:58:49

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

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

6
13.03.2009 20:24:47
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}}}
188
16.03.2009 21:53:36
У кого-нибудь возникла эта проблема, когда они перешли на python 3.x? stackoverflow.com/questions/54622935/…
jason 11.02.2019 04:38:27
@jason pickleужасен между версиями Python. Не используйте его для хранения данных, которые вы хотите сохранить. Используйте его только для кешей и других вещей, которые вы можете сбросить и восстановить по желанию. Не как метод длительного хранения или сериализации.
nosklo 11.02.2019 12:09:32
Что вы используете для хранения этих объектов? Мой объект автовивификации содержит только pandas dataframes и строку.
jason 11.02.2019 14:39:47
@jason В зависимости от данных, я люблю использовать JSON, CSV-файлы или даже sqliteбазу данных для их хранения.
nosklo 12.02.2019 06:37:20

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

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

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

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

d = defaultdict(lambda :defaultdict(defaultdict))
d[1][2][3] = 4
4
3.06.2017 21:59:48
Этот ответ работает только для трех уровней в лучшем случае. Для произвольных уровней рассмотрите этот ответ .
Acumenus 3.06.2017 21:59:06

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

# yo dawg, i heard you liked dicts                                                                      
def yodict():
    return defaultdict(yodict)
30
6.07.2012 21:41:28
@Wberry: На самом деле все, что вам нужно, это yodict = lambda: defaultdict(yodict).
martineau 14.08.2013 18:27:27
Принятая версия является подклассом dict, поэтому чтобы быть полностью эквивалентным, нам нужно x = Vdict(a=1, b=2)работать.
wberry 15.08.2013 04:46:10
@wberry: Независимо от того, что в принятом ответе, быть подклассом dictне было требованием, заявленным ФП, который только спросил «лучший способ» их реализации - и, кроме того, он не / не должен в любом случае это важно в Python.
martineau 21.03.2014 17:43:46

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

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

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

0
19.10.2012 18:47:35

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

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

Возможно, вы захотите сделать это так, чтобы новые предметы не могли быть добавлены к диктату. Легко рекурсивно преобразовать все эти defaultdicts в нормальные dicts.

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

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

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

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

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
>>>
0
23.10.2017 14:27:01