Каков наилучший способ реализации вложенных словарей?
У меня есть структура данных, которая по сути составляет вложенный словарь. Допустим, это выглядит так:
{'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()
устанавливается вручную.
22 ответа
Каков наилучший способ реализации вложенных словарей в Python?
Воплощать в жизнь __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
с другими решениями, и в каждой ситуации, где мне нужно такое поведение.
class AutoVivification(dict):
"""Implementation of perl's autovivification feature."""
def __getitem__(self, item):
try:
return dict.__getitem__(self, item)
except KeyError:
value = self[item] = type(self)()
return value
Тестирование:
a = AutoVivification()
a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6
print a
Выход:
{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}
Просто потому, что я не видел такого маленького, вот диктовка, которая выглядит так, как вам нравится, без пота:
# yo dawg, i heard you liked dicts
def yodict():
return defaultdict(yodict)
Вы можете создать файл YAML и прочитать его с помощью PyYaml.
Шаг 1: Создайте файл 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(...)
,
Поскольку у вас есть схема типа "звезда", вы можете структурировать ее больше как реляционную таблицу, а не как словарь.
import collections
class Jobs( object ):
def __init__( self, state, county, title, count ):
self.state= state
self.count= county
self.title= title
self.count= count
facts = [
Jobs( 'new jersey', 'mercer county', 'plumbers', 3 ),
...
def groupBy( facts, name ):
total= collections.defaultdict( int )
for f in facts:
key= getattr( f, name )
total[key] += f.count
Подобные вещи могут иметь большое значение для создания дизайна, подобного хранилищу данных, без накладных расходов на SQL.
Если количество уровней вложенности мало, я использую collections.defaultdict
за это:
from collections import defaultdict
def nested_dict_factory():
return defaultdict(int)
def nested_dict_factory2():
return defaultdict(nested_dict_factory)
db = defaultdict(nested_dict_factory2)
db['new jersey']['mercer county']['plumbers'] = 3
db['new jersey']['mercer county']['programmers'] = 81
С помощью defaultdict
как это позволяет избежать много грязного setdefault()
, get()
, так далее.
Это функция, которая возвращает вложенный словарь произвольной глубины:
from collections import defaultdict
def make_dict():
return defaultdict(make_dict)
Используйте это так:
d=defaultdict(make_dict)
d["food"]["meat"]="beef"
d["food"]["veggie"]="corn"
d["food"]["sweets"]="ice cream"
d["animal"]["pet"]["dog"]="collie"
d["animal"]["pet"]["cat"]="tabby"
d["animal"]["farm animal"]="chicken"
Итерируйте все с чем-то вроде этого:
def iter_all(d,depth=1):
for k,v in d.iteritems():
print "-"*depth,k
if type(v) is defaultdict:
iter_all(v,depth+1)
else:
print "-"*(depth+1),v
iter_all(d)
Это распечатывает:
- food
-- sweets
--- ice cream
-- meat
--- beef
-- veggie
--- corn
- animal
-- pet
--- dog
---- labrador
--- cat
---- tabby
-- farm animal
--- chicken
Возможно, вы захотите сделать так, чтобы новые предметы не могли быть добавлены к диктату. Легко рекурсивно конвертировать все эти defaultdict
с нормальным dict
s.
def dictify(d):
for k,v in d.iteritems():
if isinstance(v,defaultdict):
d[k] = dictify(v)
return dict(d)
Я нахожу 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)
Как и предполагали другие, реляционная база данных может быть более полезной для вас. Вы можете использовать базу данных 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"'))
Это всего лишь простой пример. Вы можете определить отдельные таблицы для штатов, округов и должностей.
defaultdict()
твой друг!
Для двумерного словаря вы можете сделать:
d = defaultdict(defaultdict)
d[1][2] = 3
Для большего размера вы можете:
d = defaultdict(lambda :defaultdict(defaultdict))
d[1][2][3] = 4
Вы можете использовать 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}}}}}
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)
Что касается "неприятных блоков 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
Для удобства перебора вложенного словаря, почему бы просто не написать простой генератор?
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, прежде чем пытаться его получить. Функция, использующая охранные предложения, может выглядеть так:
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']
Я делаю предположения о значении вашей структуры данных здесь, но должно быть легко настроить то, что вы действительно хотите сделать.
Мне нравится идея обернуть это в классе и реализовать __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
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
) и одиночные значения в противном случае.
Если ваш набор данных не останется достаточно маленьким, вы можете рассмотреть возможность использования реляционной базы данных. Это будет делать именно то, что вы хотите: упростить добавление подсчетов, выбор подмножеств подсчетов и даже совокупных подсчетов по штатам, округам, роду занятий или любой их комбинации.
Вы можете использовать рекурсию в 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})})})
Я мог бы обернуть все это в классе, но, похоже, кто-то уже сделал это.
Класс из опенсорсаndicts
package (автор я) пытается облегчить работу с вложенными словарями. Я думаю, что он отвечает всем требованиям, которые задают вопросы.
Здесь у вас есть сводка его возможностей, для получения более подробной информации обратитесь к документации .
Инициализировать
>>> from ndicts import NestedDict
>>> nd = NestedDict({"a": {"aa": 0}, "b": 1})
Получить предметы
Думайте о a, как если бы это был сплющенный словарь.
>>> nd["a", "aa"]
0
В то же время вы можете получить промежуточные узлы, а не только конечные значения.
>>> nd["a"]
{"aa": 0}
Если ключ отсутствует, генерируется исключение.
>>> nd["asd"]
Traceback (most recent call last):
...
KeyError: ('asd',)
Набор предметов
Как и в обычном словаре, если ключ отсутствует, он добавляется в файл .
>>> nd["a", "ab"] = 2
>>> nd
NestedDict({"a": {"aa": 0, "ab": 2}, "b": 1})
Это позволяет начать с пустого места, которое можно оживить, установив новые элементы.
Итерация
Когда дело доходит до итерации, подумайте оNestedDict
как плоский словарь. знакомый.keys()
,.values()
и.item()
методы доступны.
>>> [key for key in nd]
[('a', 'aa'), ('a', 'ab'), ('b',)]
>>> [value for value in nd.values()]
[0, 2, 1]
Я использовал эту функцию. это безопасно, быстро, легко обслуживаемо.
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
>>>
Для следующего (скопированного сверху) есть способ реализовать функцию добавления. Я пытаюсь использовать вложенный словарь для хранения значений в виде массива.
class Vividict(dict):
def __missing__(self, key):
value = self[key] = type(self)() # retain local pointer to value
return value
Моя текущая реализация выглядит следующим образом:
totalGeneHash=Vividict()
for keys in GenHash:
for second in GenHash[keys]:
if keys in sampleHash:
total_val = GenHash[keys][second]
totalGeneHash[gene][keys].append(total_val)
This is the error I get: AttributeError: 'Vividict' object has no attribute 'append'
У меня есть нечто подобное. У меня много случаев, когда я делаю:
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')
Лучше? Я думаю так.