Angenommen, Sie haben ein Wörterbuch wie:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Wie würden Sie das in etwas abflachen wie:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
Angenommen, Sie haben ein Wörterbuch wie:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Wie würden Sie das in etwas abflachen wie:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
Antworten:
Grundsätzlich müssen Sie auf die gleiche Weise, wie Sie eine verschachtelte Liste reduzieren würden, nur die zusätzliche Arbeit erledigen, um das Diktat nach Schlüssel / Wert zu iterieren, neue Schlüssel für Ihr neues Wörterbuch zu erstellen und das Wörterbuch im letzten Schritt zu erstellen.
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}
isinstancedurch einen try..exceptBlock ersetzen , funktioniert dies für jede Zuordnung, auch wenn es nicht von abgeleitet ist dict.
collections.MutableMapping, um es allgemeiner zu machen. Aber für Python <2.6 try..exceptist dies wahrscheinlich die beste Option.
if isinstance(v, collections.MutableMapping):zuif v and isinstance(v, collections.MutableMapping):
new_key = parent_key + sep + k if parent_key else kdavon ausgegangen wird, dass Schlüssel immer Zeichenfolgen sind, andernfalls wird sie ausgelöst TypeError: cannot concatenate 'str' and [other] objects. Sie können dies jedoch beheben, indem Sie einfach kzu string ( str(k)) zwingen oder Schlüssel zu einem Tupel anstelle eines Strings verketten (Tupel können auch Diktierschlüssel sein).
Es gibt zwei wichtige Überlegungen, die das Originalplakat berücksichtigen muss:
{'a_b':{'c':1}, 'a':{'b_c':2}}würde ergeben {'a_b_c':???}. Die folgende Lösung umgeht das Problem, indem eine iterierbare Anzahl von Paaren zurückgegeben wird.joinedKey = '_'.join(*keys), kostet Sie das O (N ^ 2) Laufzeit. Wenn Sie jedoch bereit sind zu sagen nextKey = previousKey+'_'+thisKey, erhalten Sie O (N) Zeit. Mit der folgenden Lösung können Sie beides tun (da Sie lediglich alle Schlüssel verketten und anschließend nachbearbeiten können).(Leistung ist wahrscheinlich kein Problem, aber ich werde auf den zweiten Punkt eingehen, falls sich jemand anderes darum kümmert: Bei der Implementierung gibt es zahlreiche gefährliche Möglichkeiten. Wenn Sie dies rekursiv tun und nachgeben und wieder nachgeben oder etwas Äquivalentes, das berührt Knoten mehr als einmal (was aus Versehen recht einfach ist), erledigen Sie möglicherweise eher O (N ^ 2) als O (N). Dies liegt daran, dass Sie möglicherweise dann einen Schlüssel berechnen aund a_1dann a_1_i... und dann berechnen adann a_1dann a_1_ii..., aber eigentlich sollten Sie nicht noch a_1einmal rechnen müssen . Auch wenn Sie es nicht neu berechnen, ist es genauso schlecht, es erneut zu liefern (ein "Level-by-Level" -Ansatz). Ein gutes Beispiel ist über die Leistung nachdenken auf {1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}})
Unten ist eine Funktion, die ich geschrieben habe und flattenDict(d, join=..., lift=...)die an viele Zwecke angepasst werden kann und die Sie tun können, was Sie wollen. Leider ist es ziemlich schwierig, eine faule Version dieser Funktion zu erstellen, ohne die oben genannten Leistungseinbußen zu verursachen (viele Python-Buildins wie chain.from_iterable sind nicht wirklich effizient, was ich erst nach ausführlichen Tests von drei verschiedenen Versionen dieses Codes festgestellt habe, bevor ich mich entschieden habe dieses).
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
Um besser zu verstehen, was los ist, finden Sie unten ein Diagramm für diejenigen, die mit reduce(links) nicht vertraut sind , auch bekannt als "Links falten". Manchmal wird es mit einem Anfangswert anstelle von k0 gezeichnet (nicht Teil der Liste, an die Funktion übergeben). Hier Jist unsere joinFunktion. Wir verarbeiten jedes k n mit vor 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
Dies ist in der Tat dasselbe wie functools.reduce, aber wo unsere Funktion dies für alle Schlüsselpfade des Baums tut.
>>> reduce(lambda a,b:(a,b), range(5))
((((0, 1), 2), 3), 4)
Demonstration (die ich sonst in docstring eingefügt hätte):
>>> 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}
Performance:
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
... seufz, glaube nicht, dass einer meine Schuld ist ...
[unwichtiger historischer Hinweis aufgrund von Moderationsproblemen]
In Bezug auf das angebliche Duplikat von Flatten ein Wörterbuch von Wörterbüchern (2 Ebenen tief) von Listen in Python :
Die Lösung dieser Frage kann in Bezug auf diese Frage umgesetzt werden sorted( sum(flatten(...),[]) ). Das Gegenteil ist nicht möglich: Zwar können die Werte von flatten(...)aus dem angeblichen Duplikat durch Zuordnung eines Akkumulators höherer Ordnung wiederhergestellt werden, die Schlüssel können jedoch nicht wiederhergestellt werden. (Bearbeiten: Es stellt sich auch heraus, dass die Frage des mutmaßlichen Duplikatbesitzers völlig anders ist, da sie sich nur mit Wörterbüchern befasst, die genau 2 Ebenen tief sind, obwohl eine der Antworten auf dieser Seite eine allgemeine Lösung bietet.)
Oder wenn Sie bereits Pandas verwenden, können Sie dies folgendermaßen tun 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])
Ausgabe:
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}
Wenn Sie verwenden, pandasist in pandas.io.json._normalize1 eine Funktion versteckt , nested_to_recorddie genau dies tut.
from pandas.io.json._normalize import nested_to_record
flat = nested_to_record(my_dict, sep='_')
1 In Pandas-Versionen 0.24.xund älteren Anwendungen pandas.io.json.normalize(ohne die _)
from pandas.io.json._normalize import nested_to_record. Beachten Sie den Unterstrich ( _) vor normalize.
0.25.x, ich habe die Antwort aktualisiert. :)
Hier ist eine Art "funktionale", "einzeilige" Implementierung. Es ist rekursiv und basiert auf einem bedingten Ausdruck und einem diktierten Verständnis.
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 }
Prüfung:
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)für den 2. Schlüssel in Ihren TypeError
+Operator unterstützt . Für alles andere müssen Sie sich prefix + separator + kan den entsprechenden Funktionsaufruf anpassen , um die Objekte zu erstellen.
{'a_b':{'c':1}, 'a':{'b_c':2}}
{'name': 'Steven', 'children': [{'name': 'Jessica', 'children': []}, {'name': 'George', 'children': []}]}
Code:
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,''))
Ergebnisse:
$ python test.py
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
Ich verwende Python3.2, Update für Ihre Version von Python.
lkey=''in Ihrer Funktionsdefinition angeben, anstatt die Funktion aufzurufen. Siehe andere Antworten in dieser Hinsicht.
Wie wäre es mit einer funktionalen und performanten Lösung in 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(),
{}
))
Dies ist noch performanter:
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(),
{}
))
In Benutzung:
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}
reducees großartig, wenn Sie Wörterbücher reduzieren müssen. Ich habe die Antwort aktualisiert. Sollte jetzt etwas pythonischer aussehen.
Dies ist nicht auf Wörterbücher beschränkt, sondern auf jeden Zuordnungstyp, der .items () implementiert. Weiter ist es schneller, da es eine if-Bedingung vermeidet. Trotzdem gehen die Credits an Imran:
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)
des sich nicht um einen dictbenutzerdefinierten Zuordnungstyp handelt, der nicht implementiert wird items, schlägt Ihre Funktion sofort fehl. Es funktioniert also nicht für jeden Mapping-Typ, sondern nur für diejenigen, die implementiert werden items().
items? Ich wäre neugierig, einen zu sehen.
Meine Python 3.3-Lösung mit Generatoren:
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}
Einfache Funktion zum Reduzieren verschachtelter Wörterbücher. Für Python 3, ersetzen .iteritems()mit.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
Die Idee / Anforderung war: Holen Sie sich flache Wörterbücher ohne übergeordnete Schlüssel.
Anwendungsbeispiel:
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}
Das Beibehalten von übergeordneten Schlüsseln ist ebenfalls einfach.
Dies ähnelt sowohl der Antwort von imran als auch von ralu. Es wird kein Generator verwendet, sondern eine Rekursion mit einem Abschluss verwendet:
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_dictniemals zurückgegeben wird und auch nicht erwartet wird, dass sie jemals zurückgegeben wird. Es kann stattdessen möglicherweise als Unterfunktion oder als geschlossene Funktion bezeichnet werden.
Davouds Lösung ist sehr nett, liefert aber keine zufriedenstellenden Ergebnisse, wenn das verschachtelte Diktat auch Listen von Diktaten enthält, aber sein Code muss für diesen Fall angepasst werden:
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([]), um einen Funktionsaufruf für jedes Element der zu vermeiden dict.
isinstance(v, list)stattdessen
Rekursion nutzen, einfach und lesbar halten:
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
Anruf ist einfach:
new_dict = flatten_dict(dictionary)
oder
new_dict = flatten_dict(dictionary, separator="_")
wenn wir das Standardtrennzeichen ändern wollen.
Eine kleine Aufschlüsselung:
Wenn die Funktion zum ersten Mal aufgerufen wird, wird sie nur mit der Übergabe aufgerufen, dictionarydie abgeflacht werden soll. Der accumulatorParameter dient hier zur Unterstützung der Rekursion, die wir später sehen werden. Wir instanziieren also accumulatorzu einem leeren Wörterbuch, in das wir alle verschachtelten Werte aus dem Original einfügen dictionary.
if accumulator is None:
accumulator = {}
Während wir die Werte des Wörterbuchs durchlaufen, erstellen wir für jeden Wert einen Schlüssel. Das parent_keyArgument gilt Nonefür den ersten Aufruf, während es für jedes verschachtelte Wörterbuch den Schlüssel enthält, der darauf verweist. Daher stellen wir diesen Schlüssel voran.
k = f"{parent_key}{separator}{k}" if parent_key else k
Wenn der Wert, auf vden der Schlüssel kzeigt, ein Wörterbuch ist, ruft sich die Funktion selbst auf und übergibt das verschachtelte Wörterbuch, das accumulator(das als Referenz übergeben wird, sodass alle daran vorgenommenen Änderungen in derselben Instanz vorgenommen werden) und den Schlüssel, kdamit wir kann den verketteten Schlüssel erstellen. Beachten Sie die continueAussage. Wir möchten die nächste Zeile außerhalb des ifBlocks überspringen , damit das verschachtelte Wörterbuch nicht in der accumulatorUnter-Taste landet k.
if isinstance(v, dict):
flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
continue
Was tun wir also, wenn der Wert vkein Wörterbuch ist? Legen Sie es einfach unverändert in die accumulator.
accumulator[k] = v
Sobald wir fertig sind, geben wir einfach das zurück accumulatorund lassen das ursprüngliche dictionaryArgument unberührt.
HINWEIS
Dies funktioniert nur mit Wörterbüchern, die Zeichenfolgen als Schlüssel haben. Es funktioniert mit Hash-Objekten, die die __repr__Methode implementieren , führt jedoch zu unerwünschten Ergebnissen.
Die obigen Antworten funktionieren wirklich gut. Ich dachte nur, ich würde die Unflatten-Funktion hinzufügen, die ich geschrieben habe:
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
Hinweis: Dies berücksichtigt nicht '_', das bereits in Schlüsseln vorhanden ist, ähnlich wie die abgeflachten Gegenstücke.
Hier ist ein Algorithmus für den eleganten Austausch vor Ort. Getestet mit Python 2.7 und Python 3.5. Verwenden des Punktzeichens als Trennzeichen.
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
Beispiel:
d = {'a': {'b': 'c'}}
flatten_json(d)
print(d)
unflatten_json(d)
print(d)
Ausgabe:
{'a.b': 'c'}
{'a': {'b': 'c'}}
Ich habe diesen Code hier zusammen mit der Matching- unflatten_jsonFunktion veröffentlicht.
Wenn Sie ein flach verschachteltes Wörterbuch und eine Liste aller eindeutigen Schlüssel wünschen, ist hier die Lösung:
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
Ich dachte an eine Unterklasse von UserDict, um die Schlüssel automatisch zu plattieren.
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)
Die Vorteile, dass Schlüssel ohne Überraschung im Handumdrehen oder mithilfe der Standard-Diktatinstanz hinzugefügt werden können:
>>> 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'}
Generatoren verwenden:
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'könnte durch type(i) is dictoder vielleicht sogar besser isinstance(d, dict)(oder Mapping/ MutableMapping) ersetzt werden.
Verwenden von dict.popitem () in einer einfachen Rekursion wie bei einer verschachtelten Liste:
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)}
Nicht genau das, was das OP verlangt hat, aber viele Leute kommen hierher, um nach Möglichkeiten zu suchen, verschachtelte JSON-Daten aus der realen Welt zu reduzieren, die verschachtelte Json-Objekte und Arrays mit Schlüsselwerten und Json-Objekte in den Arrays usw. enthalten können. JSON enthält keine Tupel, daher müssen wir uns darüber keine Sorgen machen.
Ich habe eine Implementierung des Kommentars zur Aufnahme von Listen durch @roneo auf die Antwort von @Imran gefunden :
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)
Probier es aus:
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}
Und das macht den Job, den ich machen muss: Ich werfe jeden komplizierten JSON darauf und es macht es für mich flacher.
Alle Credits an https://github.com/ScriptSmith .
Ich habe kürzlich ein Paket namens Cherrypicker geschrieben, um genau diese Dinge zu erledigen, da ich es so oft tun musste!
Ich denke, der folgende Code würde Ihnen genau das geben, wonach Sie suchen:
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()
Sie können das Paket installieren mit:
pip install cherrypicker
... und weitere Dokumente und Anleitungen finden Sie unter https://cherrypicker.readthedocs.io .
Andere Methoden sind möglicherweise schneller, aber die Priorität dieses Pakets besteht darin, solche Aufgaben zu vereinfachen . Wenn Sie jedoch eine große Liste von Objekten haben, die abgeflacht werden sollen, können Sie CherryPicker auch anweisen, die Parallelverarbeitung zu verwenden, um die Arbeit zu beschleunigen.
Ich bevorzuge immer den Zugriff auf dictObjekte über .items(), daher verwende ich zum Reduzieren von Diktaten den folgenden rekursiven Generator flat_items(d). Wenn Sie es noch dicteinmal haben möchten, wickeln Sie es einfach so ein: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
Variation dieser verschachtelten Wörterbücher reduzieren , Schlüssel mit max_level und benutzerdefiniertem Reduzierer komprimieren .
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)
Wenn Ihnen rekursive Funktionen nichts ausmachen, finden Sie hier eine Lösung. Ich habe mir auch erlaubt, einen Ausschlussparameter einzuschließen, falls es einen oder mehrere Werte gibt, die Sie beibehalten möchten.
Code:
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
Verwendung:
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)
Ausgabe:
{'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}}
Ich habe einige der Lösungen auf dieser Seite ausprobiert - wenn auch nicht alle -, aber diejenigen, die ich ausprobiert habe, konnten die verschachtelte Liste von Diktaten nicht verarbeiten.
Betrachten Sie ein Diktat wie dieses:
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': []
}
]
}
}
Hier ist meine provisorische Lösung:
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
welches produziert:
{
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,
}
Eine provisorische Lösung und nicht perfekt.
HINWEIS:
Es werden keine leeren Wörter wie das address: {}k / v-Paar gespeichert .
Dicts in verschachtelten Tupeln werden nicht abgeflacht - obwohl es einfach wäre, sie hinzuzufügen, wenn Python-Tupel ähnlich wie Listen funktionieren.
Verwenden python-benedictSie einfach , es ist eine Diktat-Unterklasse, die viele Funktionen bietet, einschließlich einer flattenMethode. Es ist möglich, es mit pip zu installieren:pip install python-benedict
https://github.com/fabiocaccamo/python-benedict#flatten
from benedict import benedict
d = benedict(data)
f = d.flatten(separator='_')