30 votes

Qu'est ce qu'un Pythonic façon de faire de la transformation suivante sur la liste des dicts?

J'ai une liste des dicts comme ceci:

l = [{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}]

et je voudrais obtenir une sortie de cette forme:

>>> [('foo', 'bar'), ([1,2,3,4], [5,6,7,8])]

Mais à court d' for-boucle et en appending je ne vois pas de solution. Est-il une façon plus intelligente que de faire cela?

names = []
values = []
for d in l:
    names.append(d['name'])
    values.append(d['values'])

35voto

eyllanesc Points 79506

Utiliser le générateur d'expression:

l = [{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}]
v = [tuple(k["name"] for k in l), tuple(k["values"] for k in l)]
print(v)

Sortie:

[('foo', 'bar'), ([1, 2, 3, 4], [5, 6, 7, 8])]

25voto

Kevin Points 17955

Je voudrais utiliser une compréhension de liste (un peu comme eyllanesc) si j'ai écrit ce code pour la consommation publique. Mais juste pour le fun, voici un one-liner qui n'utilise pas du tout fors.

>>> l = [{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}]
>>> list(zip(*map(dict.values, l)))
[('foo', 'bar'), ([1, 2, 3, 4], [5, 6, 7, 8])]

(Notez que cela ne fonctionne de manière fiable si les dictionnaires de préserver l'ordre d'insertion, ce qui n'est pas le cas dans toutes les versions de Python. Disponible 3.6 t-il comme un détail d'implémentation, mais il n'est garanti que le comportement de 3.7.)

La décomposition rapide de la procédure:

  • dict.valeurs renvoie un dict_values de l'objet, qui est un objet iterable contenant toutes les valeurs de la dict.
  • map prend chaque dictionnaire en l et les appels dict.valeurs, en retournant un objet iterable dict_values objets.
  • zip(*thing) est un classique de la "transposition" de la recette, qui prend un objet iterable-de-iterables et efficacement retourne en diagonale. E. g. [[a,b],[c,d]] devient [[a,c], [b,d]]. Cela met tous les noms dans un tuple, et toutes les valeurs dans un autre.
  • list convertit le zip objet dans une liste.

10voto

jpp Points 83462

Vous pouvez utiliser operator.itemgetter de garantie d' ordre des valeurs:

from operator import itemgetter

fields = ('name', 'values')
res = list(zip(*map(itemgetter(*fields), L)))

print(res)

[('foo', 'bar'), ([1, 2, 3, 4], [5, 6, 7, 8])]

Si, en supposant que Python 3.6+, vous ne pouvez pas garantir approprié d'insertion de l'ordre des dictionnaires dans votre liste d'entrée, vous devez définir explicitement un ordre que ci-dessus.

Performance

Alors qu'une liste de "tuple interprétations de la" œuvres", il devient illisible et inefficace lors d'une interrogation plus d'un couple de champs:

from operator import itemgetter

n = 10**6
L = [{'name': 'foo', 'values': [1,2,3,4], 'name2': 'zoo', 'name3': 'xyz',
      'name4': 'def'}, {'name': 'bar', 'values': [5,6,7,8], 'name2': 'bart',
      'name3': 'abc', 'name4': 'ghi'}] * n

%timeit [tuple(k["name"] for k in L), tuple(k["values"] for k in L),\
         tuple(k["name2"] for k in L), tuple(k["name3"] for k in L),
         tuple(k["name4"] for k in L)]

%timeit fields = ('name', 'values', 'name2', 'name3' ,'name4');\
        list(zip(*map(itemgetter(*fields), L)))

1 loop, best of 3: 1.25 s per loop
1 loop, best of 3: 1.04 s per loop

5voto

Kale Kundert Points 178

Cela peut ne pas être exactement ce que vous aviez à l'esprit, mais pour des données tabulaires comme cela je trouve qu' pandas est généralement la meilleure solution à long terme:

>>> import pandas as pd
>>> l = [{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}]
>>> df = pd.DataFrame(l)
  name        values
0  foo  [1, 2, 3, 4]
1  bar  [5, 6, 7, 8]

En général, vous utilisez le bloc de données directement pour tout ce que vous devez faire, mais vous pouvez également le convertir en une liste de base de données de la structure:

>>> df['name'].tolist(), df['values'].tolist()
(['foo', 'bar'], [[1, 2, 3, 4], [5, 6, 7, 8]]) 

4voto

r.ook Points 6810

Pas sûr au sujet de la performance, mais voici une autre à l'aide de zip() et le déballage:

list(zip(*[tuple(i.values()) for i in l]))

# [('foo', 'bar'), ([1, 2, 3, 4], [5, 6, 7, 8])]

Edit: Comme @DeepSpace l'a souligné, il peut encore être réduite à:

list(zip(*(i.values() for i in l)))

Voici un plus, mais plus de réponse explicite si vous souhaitez définir les commandes vous-même:

list(zip(*(tuple(map(lambda k: i.get(k), ('name', 'values'))) for i in l)))

# [('foo', 'bar'), ([1, 2, 3, 4], [5, 6, 7, 8])]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X