簡體   English   中英

在一系列dicts上進行以下轉換的Pythonic方法是什么?

[英]What is a Pythonic way of doing the following transformation on a list of dicts?

我有一個像這樣的dicts列表:

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缺乏促銷和append我沒有看到解決方案。 有比這更聰明的方法嗎?

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

使用生成器表達:

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

如果我正在編寫此代碼供公眾使用,我會使用列表理解(很像eyllanesc的)。 但只是為了好玩,這里是不使用任何一個班輪for秒。

>>> 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所有值的iterable。
  • map接受l每個字典並在其上調用dict.values,返回可迭代的dict_values對象。
  • zip(*thing)是一個經典的“轉置”配方,它采用可迭代的迭代,並有效地對角翻轉它。 例如[[a,b],[c,d]]變為[[a,c],[b,d]]。 這會將所有名稱放入一個元組,將所有值放入另一個元組中。
  • list將zip對象轉換為列表。

您可以使用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

這可能與您的想法不完全相同,但對於像這樣的表格數據,我發現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]]) 

不確定性能,但這是另一個使用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])]

使用地圖

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

第一:你的代碼很好,可讀和高效,這對我來說聽起來像Pythonic。 請注意,您可能不需要列表元組。 元組是不可變的 ,因此您無法在名稱中附加其他names

用一個單詞

如果names是唯一的,您可以將您的dicts列表轉換為大型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]}

您可以直接獲得所需信息:

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

有了熊貓

如果你正在使用大量的dicts,你可能想要考慮一下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)

這是一種遞歸方式:

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

像這樣:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM