J'ai une liste de 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 à part de for
- looping et append
ing je ne vois pas de solution. Y a-t-il un moyen plus intelligent que de faire cela?
names = []
values = []
for d in l:
names.append(d['name'])
values.append(d['values'])
Utiliser l'expression du générateur:
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])]
J'utiliserais une liste de compréhension (un peu comme celle de eyllanesc) si j'écrivais ce code pour la consommation publique. Mais juste pour le plaisir, voici un one-liner qui n’utilise pas de for
s.
>>> 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 que si les dictionnaires préservent l'ordre d'insertion, ce qui n'est pas le cas dans toutes les versions de Python. CPython 3.6 le fait en tant que détail d'implémentation, mais son comportement n'est garanti qu'à partir de la version 3.7.)
Répartition rapide du processus:
dict_values
, qui est une variable contenant toutes les valeurs de dict.map
prend chaque dictionnaire dans l
et y appelle dict.values, ce qui renvoie un objet itérable d'objets dict_values.Zip(*thing)
est une recette classique de "transposition", qui prend un itérable d’itérables et l’inverse en diagonale. Par exemple. [[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 l'objet Zip en une liste.Vous pouvez utiliser operator.itemgetter
to garantie classement 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 Python 3.6+, vous ne pouvez pas garantir un ordre d'insertion approprié des dictionnaires dans votre liste d'entrées, vous devrez définir explicitement un ordre comme ci-dessus.
Performance
Bien qu'une liste de "compréhensions de tuples" fonctionne, elle devient illisible et inefficace lorsque vous interrogez plusieurs 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
Ce n'est peut-être pas exactement ce que vous aviez à l'esprit, mais pour des données tabulaires comme celle-ci, je trouve que 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]
Généralement, vous utilisez le bloc de données directement pour tout ce que vous devez faire, mais vous pouvez également le convertir en une structure de données basée sur des listes:
>>> df['name'].tolist(), df['values'].tolist()
(['foo', 'bar'], [[1, 2, 3, 4], [5, 6, 7, 8]])
Pas sûr des performances, mais voici une autre prise utilisant Zip()
et décompactant:
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 être réduit à:
list(Zip(*(i.values() for i in l)))
Voici une réponse plus longue mais plus explicite si vous souhaitez définir vous-même les commandes:
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])]
utiliser la carte pour cela
names = Tuple(map(lambda d: d['name'], l))
values = Tuple(map(lambda d: d['values'], l))
result = [names, values]
Voici une façon récursive de le faire:
def trans(l):
if l:
res = trans(l[1:])
res[0], res[1] = (l[0]['name'],) + res[0], (l[0]['values'],) + res[1]
return res
return [(),()]
Premièrement: votre code est correct, lisible et efficace, ce qui me semble Pythonic ..__ Notez que vous ne voulez probablement pas une liste de n-uplets, cependant. Les tuples sont immuables , vous ne pourriez donc pas ajouter un autre nom à names
.
Si names
est unique, vous pouvez convertir votre liste de dict en un grand dict:
>>> l = [{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}]
>>> data = {d['name']:d['values'] for d in l}
>>> data
{'foo': [1, 2, 3, 4], 'bar': [5, 6, 7, 8]}
Vous pouvez obtenir directement les informations souhaitées:
>>> data.keys()
dict_keys(['foo', 'bar'])
>>> data.values()
dict_values([[1, 2, 3, 4], [5, 6, 7, 8]])
Si vous voulez vraiment une liste de listes:
>>> [list(data.keys()), list(data.values())]
[['foo', 'bar'], [[1, 2, 3, 4], [5, 6, 7, 8]]]
Si vous travaillez avec une longue liste de dict, tenez compte de pandas
.
Vous pouvez initialiser une DataFrame
directement:
>>> import pandas as pd
>>> df = pd.DataFrame([{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}])
>>> df
name values
0 foo [1, 2, 3, 4]
1 bar [5, 6, 7, 8]
Si vous avez besoin des noms en tant qu’itération, vous pouvez obtenir la colonne correspondante:
>>> df['name']
0 foo
1 bar
Name: name, dtype: object
Si vous avez vraiment besoin d'une liste de noms:
>>> list(df['name'])
['foo', 'bar']
Pour obtenir les noms et les valeurs ensemble:
>>> df.values.T
array([['foo', 'bar'],
[list([1, 2, 3, 4]), list([5, 6, 7, 8])]], dtype=object)