Załóżmy, że masz słownik taki jak:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Jak byś spłaszczył to do czegoś takiego:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
Załóżmy, że masz słownik taki jak:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Jak byś spłaszczył to do czegoś takiego:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
Odpowiedzi:
Zasadniczo w ten sam sposób, w jaki spłaszczyłbyś zagnieżdżoną listę, musisz po prostu wykonać dodatkową pracę, aby iterować dyktafon według klucza / wartości, tworząc nowe klucze dla nowego słownika i tworząc słownik w ostatnim kroku.
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
z try..except
bloku, to będzie pracować dla dowolnego odwzorowania, nawet jeśli nie jest pochodną dict
.
collections.MutableMapping
aby był bardziej ogólny. Ale dla Pythona <2.6 try..except
jest to prawdopodobnie najlepsza opcja.
if isinstance(v, collections.MutableMapping):
naif v and isinstance(v, collections.MutableMapping):
new_key = parent_key + sep + k if parent_key else k
zakłada się, że klucze są zawsze łańcuchami, w przeciwnym razie wzrosną TypeError: cannot concatenate 'str' and [other] objects
. Jednak możesz to naprawić, po prostu wymuszając metodę k
string ( str(k)
) lub łącząc klucze w krotkę zamiast w łańcuch (krotki również mogą być kluczami dict).
Istnieją dwie ważne kwestie, które należy wziąć pod uwagę przy oryginalnym plakacie:
{'a_b':{'c':1}, 'a':{'b_c':2}}
spowoduje to {'a_b_c':???}
. Poniższe rozwiązanie pozwala uniknąć problemu, zwracając iterowalne pary.joinedKey = '_'.join(*keys)
, będzie cię to kosztować O (N ^ 2) czasu pracy. Jeśli jednak chcesz to powiedzieć nextKey = previousKey+'_'+thisKey
, masz czas O (N). Poniższe rozwiązanie pozwala ci zrobić jedno i drugie (ponieważ możesz po prostu połączyć wszystkie klucze, a następnie przetworzyć je ponownie).(Wydajność nie jest prawdopodobnie problemem, ale omówię drugi punkt na wypadek, gdyby ktokolwiek inny się tym przejmował: wdrażając to, istnieje wiele niebezpiecznych wyborów. Jeśli robisz to rekurencyjnie i dajesz i ponownie dajesz, lub cokolwiek równoważnego, co dotyka węzłów więcej niż raz (co jest dość łatwe do przypadkowego zrobienia), potencjalnie wykonujesz pracę O (N ^ 2) zamiast O (N). To dlatego, że być może obliczasz klucz, a
a a_1
potem a_1_i
..., a potem obliczasz a
następnie a_1
następnie a_1_ii
..., ale tak naprawdę nie powinno mieć obliczyć a_1
ponownie. Nawet jeśli nie są przeliczania go ponownie otrzymując go (podejście „poziom po poziomie”) jest tak źle. dobrym przykładem jest myśleć o występie na {1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}}
)
Poniżej znajduje się funkcja, którą napisałem, flattenDict(d, join=..., lift=...)
która może być dostosowana do wielu celów i może robić, co chcesz. Niestety, dość trudno jest stworzyć leniwą wersję tej funkcji bez ponoszenia powyższych kar za wydajność (wiele wbudowanych funkcji Pythona, takich jak chain.from_iterable, nie jest w rzeczywistości wydajnych, co zdałem sobie sprawę dopiero po szeroko zakrojonych testach trzech różnych wersji tego kodu, zanim zdecydowałem się na ten).
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
Aby lepiej zrozumieć, co się dzieje, poniżej znajduje się diagram dla osób niezaznajomionych z reduce
(po lewej), znanym również jako „złóż w lewo”. Czasami jest rysowany z wartością początkową zamiast k0 (nie jest częścią listy, przekazywana do funkcji). Oto J
nasza join
funkcja. Przetwarzamy wstępnie każdy k n z 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
W rzeczywistości jest to to samo, co functools.reduce
, ale gdzie nasza funkcja robi to ze wszystkimi kluczowymi ścieżkami drzewa.
>>> reduce(lambda a,b:(a,b), range(5))
((((0, 1), 2), 3), 4)
Demonstracja (którą w innym przypadku umieściłbym w dokumentacji):
>>> 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}
Występ:
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
... westchnij, nie myśl, że to moja wina ...
[nieważna notatka historyczna z powodu problemów z moderacją]
Odnośnie domniemanego duplikatu Flatten słownika słowników (głęboki na 2 poziomy) list w Pythonie :
Rozwiązanie tego pytania można zaimplementować w ramach tego przez działanie sorted( sum(flatten(...),[]) )
. Odwrotna nie jest możliwe: ile jest prawdą, że wartość z flatten(...)
może być odzyskany z domniemanym podwójnie przez akumulator mapowania wyższego rzędu, to nie można odzyskać kluczy. (edytuj: Okazuje się również, że pytanie domniemanego powielonego właściciela jest zupełnie inne, ponieważ dotyczy tylko słowników o głębokości dokładnie 2 poziomów, chociaż jedna z odpowiedzi na tej stronie daje ogólne rozwiązanie.)
A jeśli już używasz pand, możesz to zrobić w następujący json_normalize()
sposób:
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])
Wynik:
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}
Jeśli używasz, pandas
istnieje funkcja ukryta w pandas.io.json._normalize
1 o nazwie, nested_to_record
która robi to dokładnie.
from pandas.io.json._normalize import nested_to_record
flat = nested_to_record(my_dict, sep='_')
1 W wersjach pandy 0.24.x
i starszym użyciu pandas.io.json.normalize
(bez _
)
from pandas.io.json._normalize import nested_to_record
. Zwróć uwagę na podkreślenie ( _
) przed normalize
.
0.25.x
, zaktualizowałem odpowiedź. :)
Oto rodzaj „funkcjonalnej”, „jednowierszowej” implementacji. Jest rekurencyjny i oparty na wyrażeniu warunkowym i dyktowaniu.
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 }
Test:
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. klucza w twoich rzutach testowychTypeError
+
operator. W przypadku czegokolwiek innego będziesz musiał dostosować prefix + separator + k
się do odpowiedniego wywołania funkcji, aby skomponować obiekty.
{'a_b':{'c':1}, 'a':{'b_c':2}}
{'name': 'Steven', 'children': [{'name': 'Jessica', 'children': []}, {'name': 'George', 'children': []}]}
Kod:
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,''))
Wyniki:
$ python test.py
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
Używam python3.2, aktualizacja dla twojej wersji pythona.
lkey=''
w definicji funkcji zamiast podczas wywoływania funkcji. Zobacz inne odpowiedzi w tym zakresie.
Co powiesz na funkcjonalne i wydajne rozwiązanie w Pythonie 3.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(),
{}
))
To jest jeszcze bardziej wydajne:
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(),
{}
))
W użyciu:
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
jest to świetne rozwiązanie, jeśli trzeba zmniejszyć liczbę słowników. Zaktualizowałem odpowiedź. Powinien wyglądać teraz trochę bardziej pytonicznie.
Nie jest to ograniczone do słowników, ale do każdego typu odwzorowania, który implementuje .items (). Dalej jest szybszy, ponieważ unika warunku „jeśli”. Niemniej jednak kredyty trafiają do Imrana:
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
nie jest dict
niestandardowym typem mapowania, który nie jest implementowany items
, Twoja funkcja od razu zakończy się niepowodzeniem. Więc to nie działa dla każdego typu mapowania, ale tylko te, które implementują items()
.
items
? Byłbym ciekawy, żeby taki zobaczyć.
Moje rozwiązanie dla Pythona 3.3 przy użyciu generatorów:
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}
Prosta funkcja spłaszczania zagnieżdżonych słowników. Pythona 3, zastępuje .iteritems()
się.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
Pomysł / wymóg brzmiał: Uzyskaj płaskie słowniki bez przechowywania kluczy nadrzędnych.
Przykład użycia:
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}
Przechowywanie kluczy rodzica również jest proste.
Wykorzystując rekursję, zachowując prostotę i czytelność dla człowieka:
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
Połączenie jest proste:
new_dict = flatten_dict(dictionary)
lub
new_dict = flatten_dict(dictionary, separator="_")
jeśli chcemy zmienić domyślny separator.
Mały podział:
Kiedy funkcja jest wywoływana po raz pierwszy, jest wywoływana tylko przekazując to, dictionary
co chcemy spłaszczyć. accumulator
Parametrem jest tutaj do rekursji wsparcia, które zobaczymy później. Więc tworzymy instancję accumulator
w pustym słowniku, w którym umieścimy wszystkie zagnieżdżone wartości z oryginału dictionary
.
if accumulator is None:
accumulator = {}
Podczas iteracji po wartościach słownika tworzymy klucz dla każdej wartości. parent_key
Argumentem będzie None
do pierwszego połączenia, podczas gdy dla każdego zagnieżdżonego słownika, będzie zawierać klucz wskazując na niego, więc poprzedzić ten klucz.
k = f"{parent_key}{separator}{k}" if parent_key else k
W przypadku, gdy wartość wskazywana v
przez klucz k
jest słownikiem, funkcja wywołuje samą siebie, przekazując zagnieżdżony słownik, accumulator
(który jest przekazywany przez odniesienie, więc wszystkie wprowadzone w nim zmiany są dokonywane w tej samej instancji) i klucz k
, abyśmy może skonstruować połączony klucz. Zwróć uwagę na continue
oświadczenie. Chcemy pominąć następną linię, znajdującą się poza if
blokiem, aby zagnieżdżony słownik nie znalazł się w accumulator
kluczu under k
.
if isinstance(v, dict):
flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
continue
Co więc zrobimy, jeśli wartość v
nie jest słownikiem? Po prostu włóż go bez zmian do accumulator
.
accumulator[k] = v
Kiedy skończymy, po prostu zwracamy accumulator
, pozostawiając oryginalny dictionary
argument nietknięty.
UWAGA
Działa to tylko ze słownikami, które mają ciągi znaków jako klucze. Będzie działać z obiektami, które można mieszać, implementując tę __repr__
metodę, ale przyniesie niepożądane wyniki.
Jest to podobne do odpowiedzi imrana i ralu. Nie używa generatora, ale zamiast tego wykorzystuje rekursję z zamknięciem:
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}
_flatten_dict
nigdy nie jest zwracana ani nie oczekuje się jej zwrotu. Zamiast tego może być nazywana podfunkcją lub funkcją zamkniętą .
Rozwiązanie Davouda jest bardzo ładne, ale nie daje zadowalających wyników, gdy zagnieżdżony dykt zawiera również listy dykt, ale jego kod jest dostosowany do tego przypadku:
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([])
aby uniknąć wywołania funkcji dla każdego elementu dict
.
isinstance(v, list)
zamiast tego
Powyższe odpowiedzi działają naprawdę dobrze. Pomyślałem, że dodam niespłaszczoną funkcję, którą napisałem:
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
Uwaga: to nie uwzględnia znaku „_” już obecnego w kluczach, podobnie jak ich odpowiedniki spłaszczone.
Oto algorytm eleganckiej wymiany na miejscu. Testowane w Pythonie 2.7 i Pythonie 3.5. Używanie kropki jako separatora.
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
Przykład:
d = {'a': {'b': 'c'}}
flatten_json(d)
print(d)
unflatten_json(d)
print(d)
Wynik:
{'a.b': 'c'}
{'a': {'b': 'c'}}
Opublikowałem ten kod tutaj wraz z unflatten_json
funkcją dopasowania .
Jeśli chcesz spłaszczyć zagnieżdżony słownik i chcesz mieć listę wszystkich unikalnych kluczy, oto rozwiązanie:
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
Myślałem o podklasie UserDict do automagicznego spłaszczania klawiszy.
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)
Zalety polegające na tym, że klucze można dodawać w locie lub przy użyciu standardowej instrukcji dyktowania, bez zaskoczenia:
>>> 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'}
Korzystanie z generatorów:
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'
można by zastąpić type(i) is dict
lub nawet lepiej isinstance(d, dict)
(lub Mapping
/ MutableMapping
).
Używanie dict.popitem () w prostej rekursji podobnej do zagnieżdżonej listy:
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)}
Nie jest to dokładnie to, o co prosił OP, ale wielu ludzi przychodzi tutaj, szukając sposobów na spłaszczenie zagnieżdżonych danych JSON w świecie rzeczywistym, które mogą mieć zagnieżdżone obiekty json i tablice klucz-wartość oraz obiekty JSON wewnątrz tablic i tak dalej. JSON nie zawiera krotek, więc nie musimy się tym martwić.
Znalazłem implementację komentarza włączenia listy autorstwa @roneo do odpowiedzi zamieszczonej przez @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)
Sprawdź to:
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}
I to robi to, czego potrzebuję: rzucam na to skomplikowany plik json, a to dla mnie wyrównuje.
Wszystkie kredyty do https://github.com/ScriptSmith .
Właściwie napisałem niedawno pakiet o nazwie cherrypicker, aby poradzić sobie z tego rodzaju rzeczami, ponieważ musiałem to robić tak często!
Myślę, że poniższy kod dałby ci dokładnie to, czego szukasz:
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()
Możesz zainstalować pakiet za pomocą:
pip install cherrypicker
... a więcej dokumentów i wskazówek znajdziesz na https://cherrypicker.readthedocs.io .
Inne metody mogą być szybsze, ale priorytetem tego pakietu jest, aby takie zadania łatwe . Jeśli jednak masz dużą listę obiektów do spłaszczenia, możesz również powiedzieć CherryPicker, aby używał przetwarzania równoległego w celu przyspieszenia działania.
Zawsze wolę uzyskiwać dostęp do dict
obiektów przez .items()
, więc do spłaszczania dykt używam następującego generatora rekurencyjnego flat_items(d)
. Jeśli chcesz mieć dict
ponownie, po prostu zawiń to w ten sposób: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
Odmiana tych zagnieżdżonych słowników Flatten, kompresja kluczy z maksymalnym poziomem i niestandardową redukcją.
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)
Jeśli nie masz nic przeciwko funkcjom rekurencyjnym, oto rozwiązanie. Pozwoliłem sobie również na uwzględnienie parametru wykluczenia na wypadek, gdyby istniała jedna lub więcej wartości, które chcesz zachować.
Kod:
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
Stosowanie:
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)
Wynik:
{'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}}
Wypróbowałem niektóre rozwiązania na tej stronie - choć nie wszystkie - ale te, które próbowałem, nie poradziły sobie z zagnieżdżoną listą dykt.
Rozważ taki dykt:
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': []
}
]
}
}
Oto moje prowizoryczne rozwiązanie:
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
który produkuje:
{
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,
}
Prowizoryczne rozwiązanie i nie jest doskonałe.
UWAGA:
nie zachowuje pustych dykt, takich jak address: {}
para k / v.
nie spłaszczy dykt w zagnieżdżonych krotkach - chociaż łatwo byłoby to dodać, wykorzystując fakt, że krotki Pythona działają podobnie do list.
Po prostu użyj python-benedict
, jest to podklasa dict, która oferuje wiele funkcji, w tym flatten
metodę. Można go zainstalować za pomocą pip:pip install python-benedict
https://github.com/fabiocaccamo/python-benedict#flatten
from benedict import benedict
d = benedict(data)
f = d.flatten(separator='_')