Предположим, у вас есть словарь, как:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Как бы вы пошли на то, чтобы сгладить это в нечто вроде:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
Предположим, у вас есть словарь, как:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Как бы вы пошли на то, чтобы сгладить это в нечто вроде:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
Ответы:
По сути, точно так же, как вы бы выровняли вложенный список, вам просто нужно проделать дополнительную работу для итерации dict по ключу / значению, создания новых ключей для вашего нового словаря и создания словаря на последнем этапе.
import collections
def flatten(d, parent_key='', sep='_'):
items = []
for k, v in d.items():
new_key = parent_key + sep + k if parent_key else k
if isinstance(v, collections.MutableMapping):
items.extend(flatten(v, new_key, sep=sep).items())
else:
items.append((new_key, v))
return dict(items)
>>> flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]})
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
isinstanceс try..exceptблоком, это будет работать для любого отображения, даже если он не является производным от dict.
collections.MutableMappingчтобы сделать его более общим. Но для Python <2.6 это, try..exceptвероятно, лучший вариант.
if isinstance(v, collections.MutableMapping):наif v and isinstance(v, collections.MutableMapping):
new_key = parent_key + sep + k if parent_key else kпредполагается, что ключи всегда являются строками, в противном случае он будет повышаться TypeError: cannot concatenate 'str' and [other] objects. Тем не менее, вы можете исправить это, просто вызвав kstring ( str(k)) или объединяя ключи в кортеж вместо строки (кортежи также могут быть ключами dict).
Есть два больших соображения, которые должен учитывать оригинальный постер:
{'a_b':{'c':1}, 'a':{'b_c':2}}приведет к {'a_b_c':???}. Приведенное ниже решение уклоняется от проблемы, возвращая итерацию пар.joinedKey = '_'.join(*keys), что это будет стоить вам O (N ^ 2) времени выполнения. Однако, если вы готовы сказать nextKey = previousKey+'_'+thisKey, это дает вам O (N) время. Приведенное ниже решение позволяет вам использовать оба способа (поскольку вы можете просто объединить все ключи, а затем обработать их).(Производительность вряд ли является проблемой, но я остановлюсь на втором пункте на случай, если кого-то еще это волнует: при реализации этого существует множество опасных вариантов. Если вы делаете это рекурсивно и получаете и повторно приносите, или что-нибудь эквивалентное, что касается узлы более одного раза (что довольно легко сделать случайно), вы выполняете потенциально O (N ^ 2) работу, а не O (N). Это потому, что, возможно, вы вычисляете ключ, aа a_1затем a_1_i..., а затем вычисляете aто a_1тогда a_1_ii..., но на самом деле вы не должны рассчитывать a_1снова. Даже если вы не пересчитывая его, повторно уступающее это ( «уровня по-уровень» подходом) так же плохо. хороший пример может служить думать о спектакле {1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}})
Ниже приведена функция, которую я написал, flattenDict(d, join=..., lift=...)которая может быть адаптирована для многих целей и может делать то, что вы хотите. К сожалению, довольно трудно сделать ленивую версию этой функции, не неся вышеупомянутые потери производительности (многие встроенные функции Python, такие как chain.from_iterable, на самом деле не эффективны, что я понял только после всестороннего тестирования трех различных версий этого кода, прежде чем остановиться на вот этот).
from collections import Mapping
from itertools import chain
from operator import add
_FLAG_FIRST = object()
def flattenDict(d, join=add, lift=lambda x:x):
results = []
def visit(subdict, results, partialKey):
for k,v in subdict.items():
newKey = lift(k) if partialKey==_FLAG_FIRST else join(partialKey,lift(k))
if isinstance(v,Mapping):
visit(v, results, newKey)
else:
results.append((newKey,v))
visit(d, results, _FLAG_FIRST)
return results
Чтобы лучше понять, что происходит, ниже приведена схема для тех, кто не знаком с reduce(слева), иначе называемый «сложить влево». Иногда он рисуется с начальным значением вместо k0 (не является частью списка, переданного в функцию). Здесь Jнаша joinфункция. Мы препроцессируем каждый k n с lift(k).
[k0,k1,...,kN].foldleft(J)
/ \
... kN
/
J(k0,J(k1,J(k2,k3)))
/ \
/ \
J(J(k0,k1),k2) k3
/ \
/ \
J(k0,k1) k2
/ \
/ \
k0 k1
Фактически это то же самое, что functools.reduceи наша функция, которая делает это со всеми путями дерева.
>>> reduce(lambda a,b:(a,b), range(5))
((((0, 1), 2), 3), 4)
Демонстрация (которую я бы положил в строку документации):
>>> testData = {
'a':1,
'b':2,
'c':{
'aa':11,
'bb':22,
'cc':{
'aaa':111
}
}
}
from pprint import pprint as pp
>>> pp(dict( flattenDict(testData, lift=lambda x:(x,)) ))
{('a',): 1,
('b',): 2,
('c', 'aa'): 11,
('c', 'bb'): 22,
('c', 'cc', 'aaa'): 111}
>>> pp(dict( flattenDict(testData, join=lambda a,b:a+'_'+b) ))
{'a': 1, 'b': 2, 'c_aa': 11, 'c_bb': 22, 'c_cc_aaa': 111}
>>> pp(dict( (v,k) for k,v in flattenDict(testData, lift=hash, join=lambda a,b:hash((a,b))) ))
{1: 12416037344,
2: 12544037731,
11: 5470935132935744593,
22: 4885734186131977315,
111: 3461911260025554326}
Производительность:
from functools import reduce
def makeEvilDict(n):
return reduce(lambda acc,x:{x:acc}, [{i:0 for i in range(n)}]+range(n))
import timeit
def time(runnable):
t0 = timeit.default_timer()
_ = runnable()
t1 = timeit.default_timer()
print('took {:.2f} seconds'.format(t1-t0))
>>> pp(makeEvilDict(8))
{7: {6: {5: {4: {3: {2: {1: {0: {0: 0,
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0}}}}}}}}}
import sys
sys.setrecursionlimit(1000000)
forget = lambda a,b:''
>>> time(lambda: dict(flattenDict(makeEvilDict(10000), join=forget)) )
took 0.10 seconds
>>> time(lambda: dict(flattenDict(makeEvilDict(100000), join=forget)) )
[1] 12569 segmentation fault python
... вздох, не думай, что это моя вина ...
[неважная историческая справка из-за проблем с модерацией]
Относительно предполагаемого дубликата Flatten словарь словарей (2 уровня глубиной) списков в Python :
Решение этого вопроса может быть реализовано с помощью этого sorted( sum(flatten(...),[]) ). Обратное невозможно: в то время как это верно , что значения из flatten(...)могут быть извлечены из предполагаемого дубликата путем сопоставления аккумулятора более высокого порядка, не может восстановить ключи. (Отредактируйте также: оказывается, что вопрос о предполагаемом дубликате владельца совершенно другой, поскольку он имеет дело только со словарями ровно 2-уровневого уровня, хотя один из ответов на этой странице дает общее решение.)
Или, если вы уже используете панд, вы можете сделать это json_normalize()так:
import pandas as pd
d = {'a': 1,
'c': {'a': 2, 'b': {'x': 5, 'y' : 10}},
'd': [1, 2, 3]}
df = pd.io.json.json_normalize(d, sep='_')
print(df.to_dict(orient='records')[0])
Вывод:
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}
Если вы используете pandasесть функция спрятана в pandas.io.json._normalize1 называется , nested_to_recordкоторая делает это точно.
from pandas.io.json._normalize import nested_to_record
flat = nested_to_record(my_dict, sep='_')
1 В версиях для панд 0.24.xи старых использования pandas.io.json.normalize(без _)
from pandas.io.json._normalize import nested_to_record. Обратите внимание на подчеркивание ( _) раньше normalize.
0.25.x, я обновил ответ. :)
Это своего рода «функциональная», «однострочная» реализация. Он рекурсивен и основан на условном выражении и понимании слова.
def flatten_dict(dd, separator='_', prefix=''):
return { prefix + separator + k if prefix else k : v
for kk, vv in dd.items()
for k, v in flatten_dict(vv, separator, kk).items()
} if isinstance(dd, dict) else { prefix : dd }
Тест:
In [2]: flatten_dict({'abc':123, 'hgf':{'gh':432, 'yu':433}, 'gfd':902, 'xzxzxz':{"432":{'0b0b0b':231}, "43234":1321}}, '.')
Out[2]:
{'abc': 123,
'gfd': 902,
'hgf.gh': 432,
'hgf.yu': 433,
'xzxzxz.432.0b0b0b': 231,
'xzxzxz.43234': 1321}
('hgf',2)2-го ключа в ваших тестовых броскахTypeError
+оператор. Для всего остального вам нужно будет приспособиться prefix + separator + kк соответствующему вызову функции для составления объектов.
{'a_b':{'c':1}, 'a':{'b_c':2}}
{'name': 'Steven', 'children': [{'name': 'Jessica', 'children': []}, {'name': 'George', 'children': []}]}
Код:
test = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
def parse_dict(init, lkey=''):
ret = {}
for rkey,val in init.items():
key = lkey+rkey
if isinstance(val, dict):
ret.update(parse_dict(val, key+'_'))
else:
ret[key] = val
return ret
print(parse_dict(test,''))
Полученные результаты:
$ python test.py
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
Я использую python3.2, обновление для вашей версии python.
lkey=''в вашем определении функции, а не при вызове функции. Смотрите другие ответы на этот счет.
Как насчет функционального и производительного решения в Python3.5?
from functools import reduce
def _reducer(items, key, val, pref):
if isinstance(val, dict):
return {**items, **flatten(val, pref + key)}
else:
return {**items, pref + key: val}
def flatten(d, pref=''):
return(reduce(
lambda new_d, kv: _reducer(new_d, *kv, pref),
d.items(),
{}
))
Это еще более производительно:
def flatten(d, pref=''):
return(reduce(
lambda new_d, kv: \
isinstance(kv[1], dict) and \
{**new_d, **flatten(kv[1], pref + kv[0])} or \
{**new_d, pref + kv[0]: kv[1]},
d.items(),
{}
))
В использовании:
my_obj = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y': 10}}, 'd': [1, 2, 3]}
print(flatten(my_obj))
# {'d': [1, 2, 3], 'cby': 10, 'cbx': 5, 'ca': 2, 'a': 1}
reduceэто здорово, если вам нужно уменьшить словари. Я обновил ответ. Теперь должен выглядеть немного более питоническим.
Это не ограничивается словарями, но каждым типом отображения, который реализует .items (). Далее это быстрее, так как избегает условия if. Тем не менее кредиты идут в Имран:
def flatten(d, parent_key=''):
items = []
for k, v in d.items():
try:
items.extend(flatten(v, '%s%s_' % (parent_key, k)).items())
except AttributeError:
items.append(('%s%s' % (parent_key, k), v))
return dict(items)
dэто не dictпользовательский тип отображения, который не реализует items, ваша функция сразу же потерпит неудачу. Таким образом, он работает не для каждого типа отображения, а только для тех, которые реализуют items().
items? Мне было бы любопытно увидеть один.
My Python 3.3 Solution с использованием генераторов:
def flattenit(pyobj, keystring=''):
if type(pyobj) is dict:
if (type(pyobj) is dict):
keystring = keystring + "_" if keystring else keystring
for k in pyobj:
yield from flattenit(pyobj[k], keystring + k)
elif (type(pyobj) is list):
for lelm in pyobj:
yield from flatten(lelm, keystring)
else:
yield keystring, pyobj
my_obj = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y': 10}}, 'd': [1, 2, 3]}
#your flattened dictionary object
flattened={k:v for k,v in flattenit(my_obj)}
print(flattened)
# result: {'c_b_y': 10, 'd': [1, 2, 3], 'c_a': 2, 'a': 1, 'c_b_x': 5}
Использование рекурсии, простота и удобочитаемость:
def flatten_dict(dictionary, accumulator=None, parent_key=None, separator="."):
if accumulator is None:
accumulator = {}
for k, v in dictionary.items():
k = f"{parent_key}{separator}{k}" if parent_key else k
if isinstance(v, dict):
flatten_dict(dictionary=v, accumulator=accumulator, parent_key=k)
continue
accumulator[k] = v
return accumulator
Звонить просто:
new_dict = flatten_dict(dictionary)
или
new_dict = flatten_dict(dictionary, separator="_")
если мы хотим изменить разделитель по умолчанию.
Небольшая поломка:
Когда функция вызывается впервые, она вызывается только в том случае, если dictionaryмы хотим сплющить. accumulatorПараметр здесь для поддержки рекурсии, которую мы увидим позже. Итак, мы создаем экземпляр accumulatorпустого словаря, в который мы поместим все вложенные значения из оригинала dictionary.
if accumulator is None:
accumulator = {}
Перебирая значения словаря, мы создаем ключ для каждого значения. parent_keyАргумент будет Noneдля первого вызова, в то время как для каждого вложенного словаря, он будет содержать ключ , указывающий на него, так что мы предварять этот ключ.
k = f"{parent_key}{separator}{k}" if parent_key else k
В случае, если значение, vна kкоторое указывает ключ, является словарем, функция вызывает себя, передавая вложенный словарь, accumulator(который передается по ссылке, поэтому все изменения, сделанные в нем, выполняются в одном и том же экземпляре) и ключ, kтак что мы может построить связанный ключ. Обратите внимание на continueутверждение. Мы хотим пропустить следующую строку вне ifблока, чтобы вложенный словарь не попадал в accumulatorнижнюю клавишу k.
if isinstance(v, dict):
flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
continue
Итак, что нам делать, если значение vне словарь? Просто поместите это без изменений в accumulator.
accumulator[k] = v
Как только мы закончим, мы просто возвращаем accumulator, оставляя исходный dictionaryаргумент без изменений.
НОТА
Это будет работать только со словарями, которые имеют строки в качестве ключей. Он будет работать с хешируемыми объектами, реализующими __repr__метод, но даст нежелательные результаты.
Простая функция для выравнивания вложенных словарей. Для Python 3 заменить .iteritems()на.items()
def flatten_dict(init_dict):
res_dict = {}
if type(init_dict) is not dict:
return res_dict
for k, v in init_dict.iteritems():
if type(v) == dict:
res_dict.update(flatten_dict(v))
else:
res_dict[k] = v
return res_dict
Идея / требование было: получить плоские словари без сохранения родительских ключей.
Пример использования:
dd = {'a': 3,
'b': {'c': 4, 'd': 5},
'e': {'f':
{'g': 1, 'h': 2}
},
'i': 9,
}
flatten_dict(dd)
>> {'a': 3, 'c': 4, 'd': 5, 'g': 1, 'h': 2, 'i': 9}
Хранить родительские ключи также просто.
Это похоже на ответ Имрана и Ралу. Он не использует генератор, но вместо этого использует рекурсию с замыканием:
def flatten_dict(d, separator='_'):
final = {}
def _flatten_dict(obj, parent_keys=[]):
for k, v in obj.iteritems():
if isinstance(v, dict):
_flatten_dict(v, parent_keys + [k])
else:
key = separator.join(parent_keys + [k])
final[key] = v
_flatten_dict(d)
return final
>>> print flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]})
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
Решение Давуда очень приятно, но не дает удовлетворительных результатов, когда вложенный диктат также содержит списки диктов, но его код должен быть адаптирован для этого случая:
def flatten_dict(d):
items = []
for k, v in d.items():
try:
if (type(v)==type([])):
for l in v: items.extend(flatten_dict(l).items())
else:
items.extend(flatten_dict(v).items())
except AttributeError:
items.append((k, v))
return dict(items)
type([])чтобы избежать вызова функции для каждого элемента dict.
isinstance(v, list)вместо этого
Ответы выше работают очень хорошо. Просто подумал, что я добавлю функцию unlatten, которую я написал:
def unflatten(d):
ud = {}
for k, v in d.items():
context = ud
for sub_key in k.split('_')[:-1]:
if sub_key not in context:
context[sub_key] = {}
context = context[sub_key]
context[k.split('_')[-1]] = v
return ud
Примечание: это не учитывает '_', уже присутствующее в ключах, во многом как сглаженные аналоги.
Вот алгоритм для элегантной замены на месте. Протестировано с Python 2.7 и Python 3.5. Использование символа точки в качестве разделителя.
def flatten_json(json):
if type(json) == dict:
for k, v in list(json.items()):
if type(v) == dict:
flatten_json(v)
json.pop(k)
for k2, v2 in v.items():
json[k+"."+k2] = v2
Пример:
d = {'a': {'b': 'c'}}
flatten_json(d)
print(d)
unflatten_json(d)
print(d)
Вывод:
{'a.b': 'c'}
{'a': {'b': 'c'}}
Я опубликовал этот код здесь вместе с unflatten_jsonфункцией соответствия .
Если вы хотите создать вложенный словарь и получить список всех уникальных ключей, то вот решение:
def flat_dict_return_unique_key(data, unique_keys=set()):
if isinstance(data, dict):
[unique_keys.add(i) for i in data.keys()]
for each_v in data.values():
if isinstance(each_v, dict):
flat_dict_return_unique_key(each_v, unique_keys)
return list(set(unique_keys))
def flatten(unflattened_dict, separator='_'):
flattened_dict = {}
for k, v in unflattened_dict.items():
if isinstance(v, dict):
sub_flattened_dict = flatten(v, separator)
for k2, v2 in sub_flattened_dict.items():
flattened_dict[k + separator + k2] = v2
else:
flattened_dict[k] = v
return flattened_dict
def flatten_nested_dict(_dict, _str=''):
'''
recursive function to flatten a nested dictionary json
'''
ret_dict = {}
for k, v in _dict.items():
if isinstance(v, dict):
ret_dict.update(flatten_nested_dict(v, _str = '_'.join([_str, k]).strip('_')))
elif isinstance(v, list):
for index, item in enumerate(v):
if isinstance(item, dict):
ret_dict.update(flatten_nested_dict(item, _str= '_'.join([_str, k, str(index)]).strip('_')))
else:
ret_dict['_'.join([_str, k, str(index)]).strip('_')] = item
else:
ret_dict['_'.join([_str, k]).strip('_')] = v
return ret_dict
Я думал о подклассе UserDict для автоматического выравнивания ключей.
class FlatDict(UserDict):
def __init__(self, *args, separator='.', **kwargs):
self.separator = separator
super().__init__(*args, **kwargs)
def __setitem__(self, key, value):
if isinstance(value, dict):
for k1, v1 in FlatDict(value, separator=self.separator).items():
super().__setitem__(f"{key}{self.separator}{k1}", v1)
else:
super().__setitem__(key, value)
Преимущества в том, что ключи могут быть добавлены на лету, или с использованием стандартной инстанции dict, без удивления:
>>> fd = FlatDict(
... {
... 'person': {
... 'sexe': 'male',
... 'name': {
... 'first': 'jacques',
... 'last': 'dupond'
... }
... }
... }
... )
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond'}
>>> fd['person'] = {'name': {'nickname': 'Bob'}}
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond', 'person.name.nickname': 'Bob'}
>>> fd['person.name'] = {'civility': 'Dr'}
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond', 'person.name.nickname': 'Bob', 'person.name.civility': 'Dr'}
Использование генераторов:
def flat_dic_helper(prepand,d):
if len(prepand) > 0:
prepand = prepand + "_"
for k in d:
i=d[k]
if type(i).__name__=='dict':
r = flat_dic_helper(prepand+k,i)
for j in r:
yield j
else:
yield (prepand+k,i)
def flat_dic(d): return dict(flat_dic_helper("",d))
d={'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
print(flat_dic(d))
>> {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
type(i).__name__=='dict'может быть заменен type(i) is dictили, возможно, даже лучше isinstance(d, dict)(или Mapping/ MutableMapping).
Использование dict.popitem () в простой рекурсии типа вложенного списка:
def flatten(d):
if d == {}:
return d
else:
k,v = d.popitem()
if (dict != type(v)):
return {k:v, **flatten(d)}
else:
flat_kv = flatten(v)
for k1 in list(flat_kv.keys()):
flat_kv[k + '_' + k1] = flat_kv[k1]
del flat_kv[k1]
return {**flat_kv, **flatten(d)}
Не совсем то, о чем спрашивал ОП, но многие приходят сюда в поисках способов сглаживания реальных JSON-данных, которые могут содержать вложенные объекты-значения json и массивы, а также объекты json внутри массивов и так далее. JSON не включает в себя кортежи, поэтому нам не нужно беспокоиться о них.
Я нашел реализацию комментария включения в список @roneo к ответу, опубликованному @Imran :
https://github.com/ScriptSmith/socialreaper/blob/master/socialreaper/tools.py#L8
import collections
def flatten(dictionary, parent_key=False, separator='.'):
"""
Turn a nested dictionary into a flattened dictionary
:param dictionary: The dictionary to flatten
:param parent_key: The string to prepend to dictionary's keys
:param separator: The string used to separate flattened keys
:return: A flattened dictionary
"""
items = []
for key, value in dictionary.items():
new_key = str(parent_key) + separator + key if parent_key else key
if isinstance(value, collections.MutableMapping):
items.extend(flatten(value, new_key, separator).items())
elif isinstance(value, list):
for k, v in enumerate(value):
items.extend(flatten({str(k): v}, new_key).items())
else:
items.append((new_key, value))
return dict(items)
Попробуй это:
flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3] })
>> {'a': 1, 'c.a': 2, 'c.b.x': 5, 'c.b.y': 10, 'd.0': 1, 'd.1': 2, 'd.2': 3}
И это делает ту работу, в которой я нуждаюсь: я добавляю любой сложный json в это, и это выравнивает это для меня.
Все кредиты для https://github.com/ScriptSmith .
На самом деле я недавно написал пакет под названием cherrypicker, чтобы разобраться с такими вещами, поскольку мне приходилось делать это так часто!
Я думаю, что следующий код даст вам именно то, что вы ищете:
from cherrypicker import CherryPicker
dct = {
'a': 1,
'c': {
'a': 2,
'b': {
'x': 5,
'y' : 10
}
},
'd': [1, 2, 3]
}
picker = CherryPicker(dct)
picker.flatten().get()
Вы можете установить пакет с помощью:
pip install cherrypicker
... а также другие документы и рекомендации на https://cherrypicker.readthedocs.io .
Другие методы могут быть быстрее, но приоритет этого пакета , чтобы сделать такие задачи легко . Если у вас есть большой список объектов, которые нужно сгладить, вы также можете указать CherryPicker использовать параллельную обработку для ускорения процесса.
Я всегда предпочитаю доступ к dictобъектам через .items(), поэтому для сглаживания диктов я использую следующий рекурсивный генератор flat_items(d). Если вам нравится иметь dictснова, просто оберните это так:flat = dict(flat_items(d))
def flat_items(d, key_separator='.'):
"""
Flattens the dictionary containing other dictionaries like here: /programming/6027558/flatten-nested-python-dictionaries-compressing-keys
>>> example = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
>>> flat = dict(flat_items(example, key_separator='_'))
>>> assert flat['c_b_y'] == 10
"""
for k, v in d.items():
if type(v) is dict:
for k1, v1 in flat_items(v, key_separator=key_separator):
yield key_separator.join((k, k1)), v1
else:
yield k, v
Вариант этого Flatten - вложенные словари, сжатие ключей с max_level и пользовательский редуктор.
def flatten(d, max_level=None, reducer='tuple'):
if reducer == 'tuple':
reducer_seed = tuple()
reducer_func = lambda x, y: (*x, y)
else:
raise ValueError(f'Unknown reducer: {reducer}')
def impl(d, pref, level):
return reduce(
lambda new_d, kv:
(max_level is None or level < max_level)
and isinstance(kv[1], dict)
and {**new_d, **impl(kv[1], reducer_func(pref, kv[0]), level + 1)}
or {**new_d, reducer_func(pref, kv[0]): kv[1]},
d.items(),
{}
)
return impl(d, reducer_seed, 0)
Если вы не возражаете против рекурсивных функций, вот решение. Я также позволил себе включить параметр исключения, если есть одно или несколько значений, которые вы хотите сохранить.
Код:
def flatten_dict(dictionary, exclude = [], delimiter ='_'):
flat_dict = dict()
for key, value in dictionary.items():
if isinstance(value, dict) and key not in exclude:
flatten_value_dict = flatten_dict(value, exclude, delimiter)
for k, v in flatten_value_dict.items():
flat_dict[f"{key}{delimiter}{k}"] = v
else:
flat_dict[key] = value
return flat_dict
Использование:
d = {'a':1, 'b':[1, 2], 'c':3, 'd':{'a':4, 'b':{'a':7, 'b':8}, 'c':6}, 'e':{'a':1,'b':2}}
flat_d = flatten_dict(dictionary=d, exclude=['e'], delimiter='.')
print(flat_d)
Вывод:
{'a': 1, 'b': [1, 2], 'c': 3, 'd.a': 4, 'd.b.a': 7, 'd.b.b': 8, 'd.c': 6, 'e': {'a': 1, 'b': 2}}
Я попробовал некоторые из решений на этой странице - хотя и не все - но те, которые я попробовал, не смогли обработать вложенный список dict.
Рассмотрим что-то вроде этого:
d = {
'owner': {
'name': {'first_name': 'Steven', 'last_name': 'Smith'},
'lottery_nums': [1, 2, 3, 'four', '11', None],
'address': {},
'tuple': (1, 2, 'three'),
'tuple_with_dict': (1, 2, 'three', {'is_valid': False}),
'set': {1, 2, 3, 4, 'five'},
'children': [
{'name': {'first_name': 'Jessica',
'last_name': 'Smith', },
'children': []
},
{'name': {'first_name': 'George',
'last_name': 'Smith'},
'children': []
}
]
}
}
Вот мое временное решение:
def flatten_dict(input_node: dict, key_: str = '', output_dict: dict = {}):
if isinstance(input_node, dict):
for key, val in input_node.items():
new_key = f"{key_}.{key}" if key_ else f"{key}"
flatten_dict(val, new_key, output_dict)
elif isinstance(input_node, list):
for idx, item in enumerate(input_node):
flatten_dict(item, f"{key_}.{idx}", output_dict)
else:
output_dict[key_] = input_node
return output_dict
который производит:
{
owner.name.first_name: Steven,
owner.name.last_name: Smith,
owner.lottery_nums.0: 1,
owner.lottery_nums.1: 2,
owner.lottery_nums.2: 3,
owner.lottery_nums.3: four,
owner.lottery_nums.4: 11,
owner.lottery_nums.5: None,
owner.tuple: (1, 2, 'three'),
owner.tuple_with_dict: (1, 2, 'three', {'is_valid': False}),
owner.set: {1, 2, 3, 4, 'five'},
owner.children.0.name.first_name: Jessica,
owner.children.0.name.last_name: Smith,
owner.children.1.name.first_name: George,
owner.children.1.name.last_name: Smith,
}
Временное решение, и оно не идеально.
НОТА:
он не хранит пустых кодов, таких как address: {}пара k / v.
это не сгладит разногласия во вложенных кортежах - хотя было бы легко добавить, используя тот факт, что кортежи python действуют подобно спискам.
Просто используйте python-benedict, это подкласс dict, который предлагает множество функций, включая flattenметод. Его можно установить с помощью pip:pip install python-benedict
https://github.com/fabiocaccamo/python-benedict#flatten
from benedict import benedict
d = benedict(data)
f = d.flatten(separator='_')