Каков наилучший способ реализации вложенных словарей в 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)
Но если вам нужно проверить ваши данные, результаты автоматически оживленного 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
другие решения, и имею в каждой ситуации, где мне нужно такое поведение.
Vividict
? Например,3
иlist
для диктата диктата списков, которые могут быть заполненыd['primary']['secondary']['tertiary'].append(element)
. Я мог бы определить 3 разных класса для каждой глубины, но я бы хотел найти более чистое решение.d['primary']['secondary'].setdefault('tertiary', []).append('element')
- ?? Спасибо за комплимент, но позвольте мне быть честным - я никогда не использую__missing__
- я всегда используюsetdefault
. Я, вероятно, должен обновить свое заключение / вступление ...The bad lookup will remain in the dictionary.
когда я рассматриваю возможность использования этого решения? Очень признателен. Thxsetdefault
если он вложит более двух уровней глубины. Похоже, что ни одна структура в Python не может предложить истинное оживление, как описано. Мне пришлось согласиться на два метода заявлений, один дляget_nested
&, дляset_nested
которого принимают ссылку на dict и список вложенных атрибутов.Тестирование:
Вывод:
источник
pickle
ужасен между версиями Python. Не используйте его для хранения данных, которые вы хотите сохранить. Используйте его только для кешей и других вещей, которые вы можете сбросить и восстановить по желанию. Не как метод длительного хранения или сериализации.sqlite
базу данных для их хранения.Просто потому, что я не видел такого маленького, вот диктовка, которая становится настолько вложенной, насколько вам угодно, без пота:
источник
yodict = lambda: defaultdict(yodict)
.dict
, поэтому чтобы быть полностью эквивалентным, нам нужноx = Vdict(a=1, b=2)
работать.dict
не было требованием, заявленным ФП, который только спросил «лучший способ» их реализации - и, кроме того, он не / не должен в любом случае это важно в Python.Вы можете создать файл YAML и прочитать его, используя PyYaml .
Шаг 1: Создайте файл YAML, "jobs.yml":
Шаг 2: Прочитайте это на Python
и теперь
my_shnazzy_dictionary
есть все ваши ценности. Если вам нужно было сделать это на лету, вы можете создать YAML в виде строки и передать ее вyaml.safe_load(...)
.источник
Поскольку у вас есть схема типа «звезда», вы можете структурировать ее больше как реляционную таблицу, а не как словарь.
Подобные вещи могут иметь большое значение для создания дизайна, подобного хранилищу данных, без накладных расходов на SQL.
источник
Если количество уровней вложенности мало, я использую
collections.defaultdict
для этого:Используя ,
defaultdict
как это позволяет избежать много грязныхsetdefault()
,get()
и т.д.источник
Это функция, которая возвращает вложенный словарь произвольной глубины:
Используйте это так:
Переберите все что-нибудь вроде этого:
Это распечатывает:
Возможно, вы захотите сделать это так, чтобы новые предметы не могли быть добавлены к диктату. Легко рекурсивно преобразовать все эти
defaultdict
s в обычныеdict
s.источник
Я нахожу
setdefault
довольно полезным; Он проверяет наличие ключа и добавляет его, если нет:setdefault
всегда возвращает соответствующий ключ, поэтому вы фактически обновляете значения 'd
' на месте.Когда дело доходит до итерации, я уверен, что вы могли бы написать генератор достаточно легко, если он еще не существует в Python:
источник
Как и предполагали другие, реляционная база данных может быть более полезной для вас. Вы можете использовать базу данных sqlite3 в памяти в качестве структуры данных для создания таблиц, а затем запрашивать их.
Это всего лишь простой пример. Вы можете определить отдельные таблицы для штатов, округов и должностей.
источник
collections.defaultdict
может быть подклассом, чтобы сделать вложенный диктат. Затем добавьте любые полезные итерационные методы в этот класс.источник
Что касается "неприятных блоков try / catch":
доходность
Вы можете использовать это для преобразования вашего плоского словарного формата в структурированный формат:
источник
Вы можете использовать Addict: https://github.com/mewwts/addict
источник
defaultdict()
твой друг!Для двумерного словаря вы можете сделать:
Для большего размера вы можете:
источник
Для удобства перебора вложенного словаря, почему бы просто не написать простой генератор?
Итак, если у вас есть свой составной вложенный словарь, перебор по нему становится простым:
Очевидно, ваш генератор может дать любой формат данных, который будет вам полезен.
Почему вы используете блоки try try для чтения дерева? Достаточно легко (и, вероятно, безопаснее) запросить, существует ли ключ в dict, прежде чем пытаться его получить. Функция, использующая охранные предложения, может выглядеть так:
Или, возможно, несколько многословный метод, это использовать метод get:
Но для более краткого подхода вы можете рассмотреть использование collection.defaultdict , который является частью стандартной библиотеки начиная с python 2.5.
Я делаю предположения о значении вашей структуры данных здесь, но должно быть легко настроить то, что вы действительно хотите сделать.
источник
Мне нравится идея упаковки это в классе и реализации
__getitem__
и__setitem__
таким образом, чтобы они реализовали простой язык запросов:Если вы хотите получить фантазию, вы также можете реализовать что-то вроде:
но в основном я думаю, что такое было бы очень интересно реализовать: D
источник
Если ваш набор данных не останется достаточно маленьким, вы можете рассмотреть возможность использования реляционной базы данных. Он будет делать именно то, что вы хотите: упростить добавление подсчетов, выбор подмножеств подсчетов и даже совокупные подсчеты по штатам, округам, роду занятий или любой их комбинации.
источник
Пример:
Редактировать: теперь возвращаются словари при запросах с подстановочными знаками (
None
), и одиночные значения в противном случае.источник
У меня есть нечто подобное. У меня много случаев, когда я делаю:
Но пройдя много уровней глубоко. Это ключ ".get (item, {})", так как он создаст другой словарь, если его еще нет. Тем временем я думал о способах справиться с этим лучше. Прямо сейчас, есть много
Итак, вместо этого я сделал:
Который имеет тот же эффект, если вы делаете:
Лучше? Я думаю так.
источник
Вы можете использовать рекурсию в lambdas и defaultdict, не нужно определять имена:
Вот пример:
источник
Я использовал эту функцию. это безопасно, быстро, легко обслуживаемо.
Пример :
источник