【问题标题】:How to unpack keys with list of values to multiple dictionaries to a list without overwriting?如何在不覆盖的情况下将具有多个字典的值列表的键解包到列表中?
【发布时间】:2019-09-20 14:00:59
【问题描述】:

我有一个字典列表:

data = [
    {'name': 'foo', 'scores': [2]},
    {'name': 'bar', 'scores': [4, 9, 3]},
    {'name': 'baz', 'scores': [6, 1]}
]

我想创建一个新列表,其中每个人 score 像这样分开:

list = [
    {'name': 'foo', 'scores': [2], 'score': 2},
    {'name': 'bar', 'scores': [4, 9, 3], 'score': 4},
    {'name': 'bar', 'scores': [4, 9, 3], 'score': 9},
    {'name': 'bar', 'scores': [4, 9, 3], 'score': 3},
    {'name': 'baz', 'scores': [6, 1], 'score': 6},
    {'name': 'baz', 'scores': [6, 1], 'score': 1}
]

然后我可以遍历每个row 和每个score,以创建一个新字典:

for row in data:
    scores = row['scores']  # list of values
    for score in scores:
        new_row = row
        new_row['score'] = score
        print(new_row)

这正是我想要的:

{'name': 'foo', 'scores': [2], 'score': 2}
{'name': 'bar', 'scores': [4, 9, 3], 'score': 4}
{'name': 'bar', 'scores': [4, 9, 3], 'score': 9}
{'name': 'bar', 'scores': [4, 9, 3], 'score': 3}
{'name': 'baz', 'scores': [6, 1], 'score': 6}
{'name': 'baz', 'scores': [6, 1], 'score': 1}

但是,我无法将这些词典添加到列表中。当我使用append() 函数将每个字典添加到新列表时:

list = []

for row in data:
    scores = row['scores']  # list of values
    for score in scores:
        new_row = row
        new_row['score'] = score
        list.append(new_row)

    print(list)

似乎覆盖了之前的一些项目:

[
{'name': 'foo', 'scores': [2], 'score': 2},
{'name': 'bar', 'scores': [4, 9, 3], 'score': 3},
{'name': 'bar', 'scores': [4, 9, 3], 'score': 3},
{'name': 'bar', 'scores': [4, 9, 3], 'score': 3},
{'name': 'baz', 'scores': [6, 1], 'score': 1},
{'name': 'baz', 'scores': [6, 1], 'score': 1}
]

这里发生了什么?为什么它打印行正确,但添加到列表时覆盖以前的项目?我以为append() 只是将新项目添加到列表的末尾而不更改其他项目?

【问题讨论】:

  • new_row = row 不会复制数据,它只是创建指向相同数据的引用。你可能想看看docs.python.org/2/library/copy.html
  • 请不要使用变量名如list which shadows 关键字

标签: python list dictionary


【解决方案1】:

这里new_row 总是引用当前的row 对象,这对于该行对象中的每个分数都是相同的。您需要创建一个复制当前行的新对象。使用 copy 包中的 deepcopy

from copy import deepcopy
for row in data:
    scores = row['scores']  # list of values
    for score in scores:
        new_row = deepcopy(row)
        ...

【讨论】:

    【解决方案2】:

    一个简单的列表理解怎么样,一步完成所有这些:

    In [269]: [{**d, **{'score': v}} for d in data for v in d['scores']]
    Out[269]: 
    [{'name': 'foo', 'score': 2, 'scores': [2]},
     {'name': 'bar', 'score': 4, 'scores': [4, 9, 3]},
     {'name': 'bar', 'score': 9, 'scores': [4, 9, 3]},
     {'name': 'bar', 'score': 3, 'scores': [4, 9, 3]},
     {'name': 'baz', 'score': 6, 'scores': [6, 1]},
     {'name': 'baz', 'score': 1, 'scores': [6, 1]}]
    

    解释/澄清

    这个列表理解可以满足 OP 的最终需要。我们首先使用嵌套的for 循环遍历字典列表data 中的每个字典和当前字典scores 中的每个值v

    for d in data for v in d['scores']  # order goes from left to right
    

    我们通过解包添加键 score 和值 v,然后我们还解包当前字典,因为 OP 也需要它。最后,我们使用{**d, **{'score': v}} 连接这两者,这就是我们需要实现的目标。

    连接是使用{ }dict() 完成的,因为我们从d{'score': v} 解包键和值;因此,另一种选择是:

    In [3]: [dict(**d, **{'score': v}) for d in data for v in d['scores']]
    Out[3]: 
    [{'name': 'foo', 'score': 2, 'scores': [2]},
     {'name': 'bar', 'score': 4, 'scores': [4, 9, 3]},
     {'name': 'bar', 'score': 9, 'scores': [4, 9, 3]},
     {'name': 'bar', 'score': 3, 'scores': [4, 9, 3]},
     {'name': 'baz', 'score': 6, 'scores': [6, 1]},
     {'name': 'baz', 'score': 1, 'scores': [6, 1]}]
    

    更多字典解包示例请参考peps/pep-0448/

    【讨论】:

    • 这并不能直接回答 OP 的问题。
    • 你能解释一下这个字典理解是如何工作的吗!
    • @kmario23 谢谢!我以前从未考虑过列表推导。似乎它们是在解压缩嵌套列表和字典时避免“for”循环的好方法。您介意解释一下以下连接的工作原理以及星号的作用:{**d, **{'score': v}}
    • @Alan 是的,列表推导确实是快速构建序列的非常方便和简洁的方法。请查看更新信息!
    • @Alan 还添加了一个更清晰的方法,如果有帮助的话:)
    【解决方案3】:

    上面的答案很棒。谢谢! 这里我只是简单的解释一下这个bug的原因。 我添加了两个 print():

    for score in scores:
            print(row)
            new_row = row
            new_row['score'] = score
            list.append(new_row)
            print(list)
    

    部分结果:

    ......
    {'name': 'bar', 'scores': [4, 9, 3]}
    [{'name': 'foo', 'scores': [2], 'score': 2}, {'name': 'bar', 'scores': [4, 9, 3], 'score': 4}]
    {'name': 'bar', 'scores': [4, 9, 3], 'score': 4}
    [{'name': 'foo', 'scores': [2], 'score': 2}, {'name': 'bar', 'scores': [4, 9, 3], 'score': 9}, {'name': 'bar', 'scores': [4, 9, 3], 'score': 9}]
    {'name': 'bar', 'scores': [4, 9, 3], 'score': 9}
    [{'name': 'foo', 'scores': [2], 'score': 2}, {'name': 'bar', 'scores': [4, 9, 3], 'score': 3}, {'name': 'bar', 'scores': [4, 9, 3], 'score': 3}, {'name': 'bar', 'scores': [4, 9, 3], 'score': 3}]
    ......
    

    所以现在我们可以看到whennew_row = row,它们指的是同一个对象。当 new_row 改变时, row 也会改变。列表结果是每个scores 的最后一个循环的结果。

    【讨论】:

    • 感谢您的澄清。在第二个循环中,我看到它正确地添加了行中的第一项,但在第三个循环中,它覆盖了行中的前一项。因此需要 deepcopy() 来复制对象而不是引用。
    • 不客气!我也从你的问题中学到了一些知识。感谢分享!
    猜你喜欢
    • 2022-01-13
    • 2019-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多