【问题标题】:If dicts in python are mutable, why does editing a dict contained in a second dict not change the second dict?如果python中的字典是可变的,为什么编辑包含在第二个字典中的字典不会改变第二个字典?
【发布时间】:2017-12-10 09:23:11
【问题描述】:

我是 python 新手,正在学习不可变和可变对象(由于我在 MATLAB 和 C# 方面的编码经验有限,我以前从未遇到过这个问题)。

我想知道为什么,如果 python 中的字典是可变的,那么编辑第二个字典中包含的字典不会改变第二个字典吗?

这是一个示例,其中一个字典(蝙蝠侠)被添加到一个超级英雄名称(super_hero_names)的字典中。当蝙蝠侠后来被改变时,它并没有反映在超级英雄的名字字典中。如果 dicts 像字符串一样不可变,这对我来说是有意义的,但它们是可变的,那么为什么会发生这种情况?

super_hero_names = {
    'Superman' : 'Clark Kent',
    'Spiderman' : 'Peter Parker'
}

batman = {'Batman' : 'Bruce'}

super_hero_names.update(batman)

batman['Batman'] = 'Bruce Wayne' # (edited)

print(super_hero_names)

# Output: {'Superman': 'Clark Kent', 'Spiderman': 'Peter Parker', 'Batman': 'Bruce'}

【问题讨论】:

  • "为什么,如果 python 中的字典是可变的,编辑包含在第二个字典中的字典不会改变第二个字典?"因为batman 不包含在super_hero_names 中。
  • 那么是因为使用 dict.update() 不引用 batman 对象,不像更改先前放入(引用)第二个列表的列表也会更改第二个列表?
  • .update() 将新的 插入到字典中。它不插入 references.
  • 此外,该示例有些损坏,因为您将新字典分配给 batman 变量,而不是更改旧字典,但这并没有真正改变结果。
  • 这篇文章可能对您有所帮助:Facts and myths about Python names and values,由 SO 资深人士 Ned Batchelder 撰写。

标签: python python-3.x dictionary immutability mutable


【解决方案1】:

可变名称

您的代码中的问题是字符串是不可变的:您不能将字符串'Bruce' 修改为'Bruce Wayne'。你替换它,参考就消失了。如果使用可变对象作为值,则可以达到预期的效果:

class Person:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return repr(self.name)


super_hero_names = {
    'Superman': Person('Clark Kent'),
    'Spiderman': Person('Peter Parker')
}

bruce = Person('Bruce')
batman = {'Batman': bruce}

super_hero_names.update(batman)

bruce.name = 'Bruce Wayne'

print(super_hero_names)
# {'Superman': 'Clark Kent', 'Spiderman': 'Peter Parker', 'Batman': 'Bruce Wayne'}

您在 Ruby 中的示例

Ruby 和 Python 通常具有非常相似的语法。 Ruby 字符串是可变的,因此您的代码只需少量修改即可在 Ruby 中运行:

super_hero_names = {
    'Superman' => 'Clark Kent',
    'Spiderman' => 'Peter Parker'
}

batman = {'Batman' => 'Bruce'}

super_hero_names.update(batman)

batman['Batman'] << ' Wayne' # Mutates the string, doesn't replace it!

print(super_hero_names)
# {"Superman"=>"Clark Kent", "Spiderman"=>"Peter Parker", "Batman"=>"Bruce Wayne"}

【讨论】:

  • 谢谢你,这回答了我的问题。我认为这是最直接的答案,但其他答案也很棒!
【解决方案2】:

可以通过将名称存储在列表中来实现您想要的,但由于额外的间接层,它会使代码更加混乱且效率较低。

super_hero_names = {
    'Superman' : ['Clark Kent'],
    'Spiderman' : ['Peter Parker'],
}

batman = {'Batman' : ['Bruce']}
super_hero_names.update(batman)

batman['Batman'][0] = 'Bruce Wayne'
print(super_hero_names)

输出

{'Superman': ['Clark Kent'], 'Spiderman': ['Peter Parker'], 'Batman': ['Bruce Wayne']}

我们也可以使用

batman['Batman'][0] += ' Wayne'

【讨论】:

  • 确实如此。这些值需要是可变的。你用的是列表,我用的是自定义类,但原理是一样的。
  • @EricDuminil 同意。对于这个简单的案例,您的类使代码更加冗长,但是对于更复杂的案例来说,它是干净的,并且易于扩展。
【解决方案3】:

每次创建字典batman时,实际上都是在创建字典并将其分配给变量batman

我相信你想要做的是:

super_hero_names = {
    'Superman' : 'Clark Kent',
    'Spiderman' : 'Peter Parker'}
batman = {'Batman' : 'Bruce'}
super_hero_names.update(batman)
super_hero_names['Batman'] =  'Bruce Wayne'
print(super_hero_names)

这种情况下的输出将是:

{'Superman': 'Clark Kent', 'Spiderman': 'Peter Parker', 'Batman': 'Bruce Wayne'}

【讨论】:

  • 是的,这就是我想要的,但是,我真的很想弄清楚更改原始字典会如何改变第二个字典。有没有另一种组合字典的方法可以做到这一点?
  • 没有。这两个字典从不“链接”。您可以继续为原始字典分配一个新字典,但在您进行更新后,python 认为它的工作已完成。
【解决方案4】:

我希望这能解释您的问题。当您更新字典时,它会使用另一个字典的值更新字典,它正在合并。但是当你将一个字典插入到另一个字典中时,可变性就存在了

super_hero_names = {
    'Superman' : 'Clark Kent',
    'Spiderman' : 'Peter Parker'
}

batman = {'Batman' : 'Bruce'}

super_hero_names['new'] = batman

print(super_hero_names)

batman['Batman'] = 'Bruce Wayne'

print(super_hero_names)

batman = {'Batman' : 'Bruce Wayne'}

print(super_hero_names)

输出

{'Spiderman': 'Peter Parker', 'new': {'Batman': 'Bruce'}, 'Superman': 'Clark Kent'}
{'Spiderman': 'Peter Parker', 'new': {'Batman': 'Bruce Wayne'}, 'Superman': 'Clark Kent'}
{'Spiderman': 'Peter Parker', 'new': {'Batman': 'Bruce Wayne'}, 'Superman': 'Clark Kent'}

【讨论】:

  • 谢谢,我明白了。那么,是不是就没有办法通过合并字典来实现可变性呢?
  • 不,合并是不可变的
  • 如果您理解,请通过接受答案来结束此问题 :)
【解决方案5】:

在这种情况下,update() 的行为如下:

for key, value in batman.items():
    super_hero_names[key] = value

这里,super_hero_names.update(batman) 首先检查super_hero_names 是否有“蝙蝠侠”键。如果存在,它会覆盖(或更新)该值。如果没有,它会创建 'Batman' 并将值分配给键。


添加:

这是一个示例代码。我没有使用字典作为数据容器,而是使用列表来保存多个超人字典。正如你所说,字典是可变的,所以update方法修改了super_hero_names列表中的内容。

superman = {'Superman' : 'Clark Kent'}
spiderman = {'Spiderman' : 'Peter Parker'}

super_hero_names = [
    superman,
    spiderman
]

batman = {'Batman' : 'Bruce'}
super_hero_names.append(batman)
print(super_hero_names)
# [{'Superman': 'Clark Kent'}, {'Spiderman': 'Peter Parker'}, {'Batman': 'Bruce'}]

batman.update({'Batman': 'test'})

print(super_hero_names)
# [{'Superman': 'Clark Kent'}, {'Spiderman': 'Peter Parker'}, {'Batman': 'test'}]

【讨论】:

  • 啊,谢谢!这清楚了update() 在这种情况下是如何工作的,那么是否有另一种方法可以将这两个字典结合起来以实现可变性(除了将字典放入字典中)?
  • 更新了答案。这个怎么样?
  • 但这使得在super_hero_names 中进行关键字搜索是不可能的。如果你想这样做,我建议参考@Eric Duminil 的回答。
  • 感谢您更新的答案!我选择了@Eric Duminil 的答案,因为这似乎是最直接和最有用的,但你的也回答了我的问题。我还发现update() 的解释非常有用。
猜你喜欢
  • 2022-01-24
  • 1970-01-01
  • 1970-01-01
  • 2021-03-09
  • 1970-01-01
  • 1970-01-01
  • 2020-06-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多