python récupérer - Aplatir les dictionnaires Python imbriqués, en compressant les clés




8 Answers

Fondamentalement, de la même façon que vous avez aplatir une liste imbriquée, vous devez simplement faire un travail supplémentaire pour l'itération de la dict par clé / valeur, en créant de nouvelles clés pour votre nouveau dictionnaire et en créant le dictionnaire à la dernière étape.

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}
élément ajouter

Supposons que vous avez un dictionnaire comme:

{'a': 1,
 'c': {'a': 2,
       'b': {'x': 5,
             'y' : 10}},
 'd': [1, 2, 3]}

Comment voulez-vous l'aplatir en quelque chose comme:

{'a': 1,
 'c_a': 2,
 'c_b_x': 5,
 'c_b_y': 10,
 'd': [1, 2, 3]}



Voici une sorte de mise en œuvre «fonctionnelle», «one-liner». Il est récursif et basé sur une expression conditionnelle et une compréhension dictée.

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 }

Tester:

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}



Ou si vous utilisez déjà des pandas, vous pouvez le faire avec json_normalize() comme ceci:

import pandas as pd

df = pd.io.json.json_normalize({'a': 1,
                                 'c': {'a': 2,
                                       'b': {'x': 5,
                                             'y' : 10}},
                                 'd': [1, 2, 3]})
print(df.to_dict(orient='records')[0])

Sortie:

{'a': 1, 'c.a': 2, 'c.b.x': 5, 'c.b.y': 10, 'd': [1, 2, 3]}

Si tu ne veux pas . séparateur, vous pouvez le pirater comme ça:

d = df.to_dict(orient='records')[0]
print dict(zip([x.replace('.', '_') for x in d.keys()], d.values()))

Sortie:

{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}



Que diriez-vous d'une solution fonctionnelle et performante dans 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(), 
        {}
    ))

C'est encore plus performant:

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(), 
        {}
    ))

Utilisé:

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}



La solution de Davoud est très belle mais ne donne pas de résultats satisfaisants lorsque la dict imbriquée contient aussi des listes de dicts, mais son code est adapté pour ce cas:

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)



Ma solution Python 3.3 utilisant des générateurs:

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}



Fonction simple pour aplatir les dictionnaires imbriqués. Pour Python 3, remplacez .iteritems() par .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

L'idée / l'exigence était la suivante: Obtenez des dictionnaires plats sans clés parentales.

Exemple d'utilisation:

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}

Garder les clés parentales est aussi simple.




Si vous utilisez des pandas il y a une fonction cachée dans pandas.io.json.normalize appelée nested_to_record qui fait cela exactement.

from pandas.io.json.normalize import nested_to_record    

flat = nested_to_record(my_dict, sep='_')



Related

python dictionary