如果Python中的字典是可变的,为什么编辑包含在第二个字典中的字典不会改变第二个字典?

5
我刚开始学习Python,正在了解不可变对象和可变对象(在我的有限编程经验中,从未遇到过这种情况,比如MATLAB和C#)。
我想知道为什么,如果Python中的字典是可变的,那么编辑包含在第二个字典中的字典为什么不会改变第二个字典?
下面是一个示例,其中将一个字典(蝙蝠侠)添加到超级英雄名称的字典中。当后来更改蝙蝠侠时,它没有反映在超级英雄名称字典中。如果字典像字符串一样是不可变的,那么这对我来说是有意义的,但它们是可变的,所以为什么会发生这种情况?
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'}

1
为什么在Python中,如果字典是可变的,那么编辑包含在第二个字典中的字典不会改变第二个字典呢?因为batman没有包含在super_hero_names中。 - juanpa.arrivillaga
那是因为使用dict.update()不会引用batman对象,不像更改之前放入(引用)第二个列表中的列表也会更改第二个列表一样。 - Jakey
.update() 方法将新的 插入字典中,而不是插入 引用 - JJJ
2
此外,示例有些错误,因为您将一个新字典分配给batman变量,而不是更改旧字典,但这并不会真正改变结果。 - JJJ
好的,谢谢!我刚刚编辑了问题,修复了那个破损的部分(我想)。 - Jakey
1
你可能会发现这篇文章有帮助:关于Python名称和值的事实与神话,它是由SO老手Ned Batchelder撰写的。 - PM 2Ring
5个回答

7

可变名称

你代码中的问题在于字符串是不可变的:你不能将字符串'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"}

谢谢,这回答了我的问题。我认为这是最直接的答案,但其他答案也非常好! - Jakey

1
你可以通过将名称存储在列表中来实现你想要的内容,但由于额外的间接层,这会使代码变得更加混乱和低效。
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'

1
确实。这些值需要是可变的。你使用了一个列表,而我使用了一个自定义类,但原则是相同的。 - Eric Duminil
@EricDuminil 同意。对于这种简单情况,您的类使代码有点冗长,但它很干净,并且可以轻松地扩展到更复杂的情况。 - PM 2Ring

0
每次创建字典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'}

是的,那个做到了我想要的,但是我真的想弄清楚改变原始字典如何改变第二个字典。有没有另一种方法可以合并字典来实现这一点? - Jakey
不,这两个字典从未“链接”在一起。你可以将一个新字典分配给原始字典,但是在更新后,Python 就认为它的工作完成了。 - RussellB.

0

希望这个解释回答了您的问题。当您更新一个字典时,它会用另一个字典的值来更新字典,这就是合并。但如果您将一个字典插入到其他字典中,则会存在可变性。

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'}

谢谢,我懂了那个解释。那么,通过合并字典没有任何可变性的方法吗? - Jakey
不,合并是不可变的。 - Tilak Putta
如果您已经理解,请通过接受答案来关闭此问题 :) - Tilak Putta

0
在这种情况下,update() 的行为如下:
for key, value in batman.items():
    super_hero_names[key] = value

在这里,super_hero_names.update(batman) 首先检查 super_hero_names 是否有 'Batman' 键。如果存在,则覆盖(或更新)该值。如果不存在,则创建 'Batman' 并将值分配给该键。

添加:

这里是一个示例代码。我使用列表来保存多个超人字典,而不是使用字典作为数据容器。正如你所说,字典是可变的,因此更新方法会修改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()在这种情况下的工作方式,那么除了把一个字典放在另一个字典中之外,还有其他可以组合这两个字典以实现可变性的方法吗? - Jakey
更新的答案。这个怎么样? - dkato
1
但这样做会使在super_hero_names中进行关键字搜索变得不可能。如果您想要这样做,我建议参考@Eric Duminil的答案。 - dkato
非常感谢您更新的答案!我选择了@Eric Duminil的答案,因为它似乎是最直接和有用的,但是您的回答也解答了我的问题。我还发现了update()的解释非常有启发性。 - Jakey

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