Припустимо, у вас є словник типу:
{'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]}
Відповіді:
В основному так само, як ви б вирівняли вкладений список, просто потрібно виконати додаткову роботу для ітерації дікта за ключем / значенням, створення нових ключів для вашого нового словника та створення словника на останньому кроці.
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
. Однак ви можете це виправити, просто примусивши k
до string ( str(k)
) або об'єднавши ключі в кортеж замість рядка (кортежі також можуть бути диктантними ключами).
Є два важливі міркування, які слід враховувати в оригінальному плакаті:
{'a_b':{'c':1}, 'a':{'b_c':2}}
це призведе до {'a_b_c':???}
. Наведене нижче рішення ухиляється від проблеми, повертаючи ітерабельний пар.joinedKey = '_'.join(*keys)
, це обійдеться вам за час роботи (O ^ N). Однак якщо ви готові сказати 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=...)
яка може бути адаптована до багатьох цілей і може робити все, що завгодно. На жаль, досить важко зробити ледачу версію цієї функції, не несучи вищезазначених штрафних санкцій (багато вбудованих пітонів, таких як ланцюг.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)
Демонстрація (яку я б інакше вклав у docstring):
>>> 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._normalize
1, що називається, 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)
другого ключа у ваших тестових 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 Рішення з використанням генераторів:
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}
Проста функція для вирівнювання вкладених словників. Для 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(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
під клавішею under k
.
if isinstance(v, dict):
flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
continue
Отже, що ми робимо, якщо значення v
не є словником? Просто помістіть його незмінним всередині accumulator
.
accumulator[k] = v
Як тільки ми закінчимо, ми просто повернемо accumulator
, залишивши оригіналdictionary
аргумент недоторканим.
ПРИМІТКА
Це буде працювати лише зі словниками, які містять рядки як ключі. Він буде працювати з доступними об'єктами, що реалізують __repr__
метод, але дасть небажані результати.
Це схоже і на відповідь імрана і на ралу. Він не використовує генератор, а натомість використовує рекурсію із закриттям:
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)
замість цього
Наведені вище відповіді дуже добре працюють. Просто подумав, що я додам незакріплену функцію, яку я написав:
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)
Переваги цього ключа в тому, що клавіші можна додавати під час руху, або використовуючи стандартний екземпляр, без здивування:
>>> 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}}
Я спробував деякі рішення на цій сторінці - хоча і не всі - але ті, з якими я намагався, не впоралися з вкладеним списком дикту.
Розглянемо такий крес:
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='_')