在一系列dicts上进行以下转换的Pythonic方法是什么?

oar*_*ish 30 python dictionary list python-3.x

我有一个像这样的dicts列表:

l = [{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}]
Run Code Online (Sandbox Code Playgroud)

我想获得这种形式的输出:

>>> [('foo', 'bar'), ([1,2,3,4], [5,6,7,8])]
Run Code Online (Sandbox Code Playgroud)

但是缺乏for环比和append我没有看到解决方案.有比这更聪明的方法吗?

names = []
values = []
for d in l:
    names.append(d['name'])
    values.append(d['values'])
Run Code Online (Sandbox Code Playgroud)

eyl*_*esc 33

使用生成器表达:

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)
Run Code Online (Sandbox Code Playgroud)

输出:

[('foo', 'bar'), ([1, 2, 3, 4], [5, 6, 7, 8])]
Run Code Online (Sandbox Code Playgroud)

  • @DeepSpace我认为肉眼可见,有2个循环.:-) (16认同)
  • 值得注意的是,这个解决方案(很可能每个单线程)需要在整个列表上进行2次迭代(快速浏览可能并不明显) (9认同)
  • 这是一个生成器表达式. (2认同)
  • 是的,如果用方括号括起来,则表示列表理解。列表理解->返回列表。字典理解->返回字典。集合理解->返回集合。生成器表达式->返回生成器。 (2认同)
  • @DeepSpace在带有`5e6`条目的列表中,此方法的速度大约是简单for循环的两倍. (2认同)
  • @DeepSpace:这是真的(至少大部分 - 在我的测试中它的速度不到2倍); 一个普通的循环必须查找列表的名称,然后是append方法,然后调用函数 - 在列表解析中,append是一个操作码.(如果你缓存`names_append = names.append`,循环仍然需要进行单个名称查找和函数调用 - 在这种情况下,在我的测试中,优化循环将略微胜过这个双元组(<generator expression) >)`回答,但仍然比两个列表推导慢10-30%) (2认同)

Kev*_*vin 24

如果我正在编写此代码供公众使用,我会使用列表理解(很像eyllanesc的).但只是为了好玩,这里是一个不使用任何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])]
Run Code Online (Sandbox Code Playgroud)

(请注意,这只有在字典保留插入顺序时才能可靠地工作,而在所有版本的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对象转换为列表.

  • `map`往往被认为是[考虑](https://www.python.org/dev/peps/pep-0279/)[un- pythonic](/sf/answers/768167221/),这是标题中要求的是什么. (5认同)

jpp*_*jpp 10

您可以使用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])]
Run Code Online (Sandbox Code Playgroud)

如果,假设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
Run Code Online (Sandbox Code Playgroud)

  • 我不知道`itemgetter`可以一次获取多个值.很好的答案!假设以后不需要字段列表,那就不会`map(itemgetter('name','values'),L))`work也一样? (2认同)

Kal*_*ert 5

这可能不是你想到的那样,但对于像这样的表格数据,我发现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]
Run Code Online (Sandbox Code Playgroud)

通常,您可以直接使用数据框来执行任何操作,但您也可以将其转换为基于列表的数据结构:

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