Как Pythonic делает следующее преобразование в списке диктов? - программирование
Подтвердить что ты не робот

Как Pythonic делает следующее преобразование в списке диктов?

У меня есть список таких слов:

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

и я хотел бы получить вывод этой формы:

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

Но если не for -looping и append я не вижу решения. Есть ли умнее, чем делать это?

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

Ответ 1

Используйте выражение генератора:

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)

Выход:

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

Ответ 2

Я бы использовал понимание списка (очень похожее на eyllanesc), если бы писал этот код для публичного использования. Но просто для удовольствия, здесь один вкладыш, который не использует 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])]

(Обратите внимание, что это надежно работает только в том случае, если словари сохраняют порядок вставки, что не всегда во всех версиях Python. CPython 3.6 делает это как деталь реализации, но это только гарантированное поведение с 3.7.)

Быстрая разбивка процесса:

  • dict.values возвращает объект dict_values, который является итеративным и содержит все значения dict.
  • map принимает каждый словарь в l и вызывает для него dict.values, возвращая итерируемые объекты dict_values.
  • zip(*thing) - это классический рецепт "транспонирования", который принимает итерацию итераций и эффективно переворачивает его по диагонали. Например, [[a, b], [c, d]] становится [[a, c], [b, d]]. Это помещает все имена в один кортеж, а все значения - в другой.
  • list преобразует объект zip в список.

Ответ 3

Вы можете использовать operator.itemgetter чтобы гарантировать порядок значений:

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])]

Если, предполагая Python 3. 6+, вы не можете гарантировать надлежащий порядок вставки словарей в вашем входном списке, вам нужно будет явно определить порядок, как указано выше.

Спектакль

Несмотря на то, что список "пониманий кортежей" работает, он становится нечитабельным и неэффективным при запросах более чем к нескольким полям:

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

Ответ 4

Возможно, это не совсем то, что вы имели в виду, но для табличных данных, подобных этой, я считаю, что pandas обычно являются лучшим решением в долгосрочной перспективе:

>>> 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]

Обычно вы используете фрейм данных напрямую для всего, что вам нужно сделать, но вы также можете преобразовать его в структуру данных на основе списка:

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

Ответ 5

Не уверен насчет производительности, но вот еще один пример использования zip() и распаковки:

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

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

Редактировать: как указывало @DeepSpace, его можно уменьшить до:

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

Здесь более длинный, но более четкий ответ, если вы хотите определить заказы самостоятельно:

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])]

Ответ 6

используйте карту для этого

names = tuple(map(lambda d: d['name'], l))
values = tuple(map(lambda d: d['values'], l))
result = [names, values]

Ответ 7

Первое: ваш код в порядке, удобочитаем и эффективен, что звучит для меня как Pythonic. Обратите внимание, что вам, вероятно, не нужен список кортежей. Кортежи являются неизменяемыми, поэтому вы не сможете добавить другое имя к names.

С одним диктом

Если names уникальны, вы можете преобразовать свой список слов в большой:

>>> 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]}

Вы можете получить необходимую информацию напрямую:

>>> data.keys()
dict_keys(['foo', 'bar'])
>>> data.values()
dict_values([[1, 2, 3, 4], [5, 6, 7, 8]])

Если вы действительно хотите список списков:

>>> [list(data.keys()), list(data.values())]
[['foo', 'bar'], [[1, 2, 3, 4], [5, 6, 7, 8]]]

С пандами

Если вы работаете с большим списком диктов, вы можете рассмотреть pandas.

Вы можете инициализировать DataFrame напрямую:

>>> 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]

Если вам нужны имена как итеративные, вы можете получить соответствующий столбец:

>>> df['name']
0    foo
1    bar
Name: name, dtype: object

Если вам действительно нужен список имен:

>>> list(df['name'])
['foo', 'bar']

Чтобы получить имена и значения вместе:

>>> df.values.T
array([['foo', 'bar'],
       [list([1, 2, 3, 4]), list([5, 6, 7, 8])]], dtype=object)

Ответ 8

Вот рекурсивный способ сделать это:

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 [(),()]

Ответ 9

Именно так:

(lambda f:
    lambda l, r=[(), ()]: f(f, l, r)
)(lambda g, l, r:
    r if len(l) == 0  else g(g, l[1:], [r[0]+(l[0]['name'],), r[1]+(l[0]['values'],)])
)([
    {'name': 'foo', 'values': [1, 2, 3, 4]},
    {'name': 'bar', 'values': [5, 6, 7, 8]},
    {'name': 'baz', 'values': [9, 9, 9, 9]}
])

Результат:

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