Python - 将字典列表展开为一维列表

33

字典列表:

data = [{
         'a':{'l':'Apple',
                'b':'Milk',
                'd':'Meatball'},
         'b':{'favourite':'coke',
              'dislike':'juice'}
         },
         {
         'a':{'l':'Apple1',
                'b':'Milk1',
                'd':'Meatball2'},
         'b':{'favourite':'coke2',
              'dislike':'juice3'}
         }, ...
]

我需要合并所有嵌套的字典,以达到期望的输出:

 [{'d': 'Meatball', 'b': 'Milk', 'l': 'Apple', 'dislike': 'juice', 'favourite': 'coke'},
  {'d': 'Meatball2', 'b': 'Milk1', 'l': 'Apple1', 'dislike': 'juice3', 'favourite': 'coke2'}]

我尝试使用嵌套列表推导式,但无法将字典连接在一起:

L = [y for x in data for y in x.values()]
print (L)

[{'d': 'Meatball', 'b': 'Milk', 'l': 'Apple'}, 
 {'dislike': 'juice', 'favourite': 'coke'}, 
{'d': 'Meatball2', 'b': 'Milk1', 'l': 'Apple1'}, 
 {'dislike': 'juice3', 'favourite': 'coke2'}]

我正在寻找最快的解决方案。


这个链接how-to-merge-two-dictionaries-in-a-single-expression可以帮到你。 - pe-perry
4个回答

25
你可以使用itertools.chain 来执行以下操作:
>>> from itertools import chain
# timeit: ~3.40
>>> [dict(chain(*map(dict.items, d.values()))) for d in data]
[{'l': 'Apple', 
  'b': 'Milk', 
  'd': 'Meatball', 
  'favourite': 'coke', 
  'dislike': 'juice'}, 
 {'l': 'Apple1', 
  'b': 'Milk1', 
  'dislike': 'juice3', 
  'favourite': 'coke2', 
  'd': 'Meatball2'}]

chainmap* 的使用使得这个表达式成为以下双重嵌套推导式的简写形式,在我的系统上实际表现更好(Python 3.5.2),并且长度并不长:

# timeit: ~2.04
[{k: v for x in d.values() for k, v in x.items()} for d in data]
# Or, not using items, but lookup by key
# timeit: ~1.67
[{k: x[k] for x in d.values() for k in x} for d in data]

注意:

RoadRunner's 循环更新的方法在timeit:~1.37方面优于这两个一行代码。


2
我喜欢你的公平行为,因为你添加了另一个更好的解决方案的时间,所以不需要重新接受 ;) 谢谢。 - jezrael
2
@jezrael 谢谢,不用隐藏这个事实。比较这三种风格迥异的方法是很有趣的,可以看到直接循环胜过理解和特别是内置函数和itertools的厨房水槽 :) - user2390182
2
一个更易读的替代方案是 [ChainMap(*d.values()) for d in data],虽然它比其他方法慢。 - Eric Duminil
为什么当我想要展开 [{k: v for x in d.values() for k, v in x.items()} for d in data] 的输出时,它会报错 TypeError: descriptor 'items' requires a 'dict' object but received a 'str'?这个结果与输入数据类似。 - abdoulsn

23
你可以使用2个嵌套循环,并使用 dict.update() 将内部字典添加到临时字典中,最后再添加:
L = []
for d in data:
    temp = {}
    for key in d:
        temp.update(d[key])

    L.append(temp)

# timeit ~1.4
print(L)

输出哪些内容:

[{'l': 'Apple', 'b': 'Milk', 'd': 'Meatball', 'favourite': 'coke', 'dislike': 'juice'}, {'l': 'Apple1', 'b': 'Milk1', 'd': 'Meatball2', 'favourite': 'coke2', 'dislike': 'juice3'}]

7
你可以使用functools.reduce和一个简单的列表推导式来展开字典列表。
>>> from functools import reduce 

>>> data = [{'b': {'dislike': 'juice', 'favourite': 'coke'}, 'a': {'l': 'Apple', 'b': 'Milk', 'd': 'Meatball'}}, {'b': {'dislike': 'juice3', 'favourite': 'coke2'}, 'a': {'l': 'Apple1', 'b': 'Milk1', 'd': 'Meatball2'}}]
>>> [reduce(lambda x,y: {**x,**y},d.values()) for d in data]
>>> [{'dislike': 'juice', 'l': 'Apple', 'd': 'Meatball', 'b': 'Milk', 'favourite': 'coke'}, {'dislike': 'juice3', 'l': 'Apple1', 'd': 'Meatball2', 'b': 'Milk1', 'favourite': 'coke2'}]

时间基准如下:

>>> import timeit
>>> setup = """
      from functools import reduce
      data = [{'b': {'dislike': 'juice', 'favourite': 'coke'}, 'a': {'l': 'Apple', 'b': 'Milk', 'd': 'Meatball'}}, {'b': {'dislike': 'juice3', 'favourite': 'coke2'}, 'a': {'l': 'Apple1', 'b': 'Milk1', 'd': 'Meatball2'}}]
  """
>>> min(timeit.Timer("[reduce(lambda x,y: {**x,**y},d.values()) for d in data]",setup=setup).repeat(3,1000000))
>>> 1.525032774952706

我的机器上其他答案的时间基准

>>> setup = """
        data = [{'b': {'dislike': 'juice', 'favourite': 'coke'}, 'a': {'l': 'Apple', 'b': 'Milk', 'd': 'Meatball'}}, {'b': {'dislike': 'juice3', 'favourite': 'coke2'}, 'a': {'l': 'Apple1', 'b': 'Milk1', 'd': 'Meatball2'}}]
    """
>>> min(timeit.Timer("[{k: v for x in d.values() for k, v in x.items()} for d in data]",setup=setup).repeat(3,1000000))
>>> 2.2488374650129117

>>> min(timeit.Timer("[{k: x[k] for x in d.values() for k in x} for d in data]",setup=setup).repeat(3,1000000))
>>> 1.8990078769857064

>>> code = """
      L = []
      for d in data:
          temp = {}
          for key in d:
              temp.update(d[key])

          L.append(temp)
    """

>>> min(timeit.Timer(code,setup=setup).repeat(3,1000000))
>>> 1.4258553800173104

>>> setup = """
      from itertools import chain
      data = [{'b': {'dislike': 'juice', 'favourite': 'coke'}, 'a': {'l': 'Apple', 'b': 'Milk', 'd': 'Meatball'}}, {'b': {'dislike': 'juice3', 'favourite': 'coke2'}, 'a': {'l': 'Apple1', 'b': 'Milk1', 'd': 'Meatball2'}}]
    """
>>> min(timeit.Timer("[dict(chain(*map(dict.items, d.values()))) for d in data]",setup=setup).repeat(3,1000000))
>>> 3.774383604992181

4

如果你有嵌套字典,只有'a'和'b'键,那么我建议采用以下解决方案,它非常快速且易于理解(为了提高可读性):

L = [x['a'] for x in data]
b = [x['b'] for x in data]

for i in range(len(L)):
    L[i].update(b[i])

# timeit ~1.4

print(L)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接