【问题标题】:Why do changes to a nested dict inside dict2 affect dict1? [duplicate]为什么更改 dict2 内的嵌套 dict 会影响 dict1? [复制]
【发布时间】:2018-10-30 22:03:29
【问题描述】:

我不明白这些情况:

content = {'a': {'v': 1}, 'b': {'v': 2}}
d1 = {'k1': {}}
d2 = {'k2': {}}
d1['k1'].update(content)
print(d1)
content['a']['v'] = 3
content['b']['v'] = 4
d2['k2'].update(content)
print(d2)
print(d1)
>>> {'k1': {'a': {'v': 1}, 'b': {'v': 2}}}
>>> {'k2': {'a': {'v': 3}, 'b': {'v': 4}}}
>>> {'k1': {'a': {'v': 3}, 'b': {'v': 4}}}

在上述情况下,在变量 content 更新后,d1 的内容发生了变化。

content = {'a': 1, 'b': 2}
d1 = {'k1': {}}
d2 = {'k2': {}}
d1['k1'].update(content)
print(d1)
content['a'] = 3
content['b'] = 4
d2['k2'].update(content)
print(d2)
print(d1)
>>> {'k1': {'a': 1, 'b': 2}}
>>> {'k2': {'a': 3, 'b': 4}}
>>> {'k1': {'a': 1, 'b': 2}} 

但是在这种情况下,即使变量 content 已更改,d1 也不会更改。我不明白为什么……有什么想法吗?

【问题讨论】:

  • 我认为所有这些赞成票但没有 cmets/answers 是因为人们也认为这是一种奇怪的行为。 :P
  • @MateenUlhaq 不是,这与dict 是可变对象而int 是不可变的事实有关。这使得 python 以不同的方式处理它们。如果内容是list(可变),也会发生类似的事情。
  • 第二个例子不是也改变了字典(content)吗?
  • @MateenUlhaq 不,在第一个示例中,键指向 dict,在第二个示例中,它们指向 int。令人困惑的是这些字典的嵌套性质,但在后台,所有内容都由一堆一旦调用就会解析的指针组成。
  • @MateenUlhaq 如果我善于解释这一点,我会发布答案:)

标签: python dictionary nested mutability


【解决方案1】:

shallow vs deep复制。

这里的副本是浅副本,因此第一级条目是副本,但嵌套结构是引用。

  • 浅拷贝构造一个新的复合对象,然后(在可能的范围内)向其中插入对对象的引用 在原文中找到。
  • 深拷贝构造一个新的复合对象,然后递归地将在
    中找到的对象的副本插入其中 原创。

【讨论】:

  • 请不要重复回答。
  • 我投票决定关闭它的价值。
【解决方案2】:

如果我们用一个简单的赋值替换update()

# d1['k1'].update(content)
d1['k1'] = content

我们得到:

{'k1': {'a': 1, 'b': 2}}
{'k2': {'a': 3, 'b': 4}}
{'k1': {'a': 3, 'b': 4}}

(这与 update 在您的示例中所做的不同。)这是因为 update 接受一个可迭代对象(例如字典)并复制键值对内部。相当于做:

d1['k1'] = {k: v for k, v in content.items()}

当然,int 值是不可变的,因此它们的重新分配不会影响原始值。

【讨论】:

    【解决方案3】:

    您的两个 sn-ps 之间的主要区别在于 content['a']['v'] = 3content['a'] = 3 是完全不同的操作。在第一种情况下,您通过更改其v修改内部字典。在后一种情况下,您替换字典中的值而不修改它。

    当一切都是字典时,这很令人困惑,所以让我们将字典替换为变量和类的实例:

    class Person:
        def __init__(self, name):
            self.name = name
    
    # these two variables are represent your `content` dict
    a = Person('Andy')  # this variable represents `{'v': 1}`
    b = Person('Belle')  # this variable represents `{'v': 2}`
    
    # the equivalent of `d1['k1'].update(content)` is a simple assignment
    k1_a = a
    
    # and the equivalent of `content['a']['v'] = 3` is changing a's name
    a.name = 'Aaron'
    
    # because k1_a and a are the same Person instance, this is reflected in k1_a:
    print(k1_a.name)  # output: Aaron
    

    这里要注意的关键点是

    1. k1_a = a 不会复制 Person;类似于d1['k1'].update(content) 不会复制{'v': 1} dict。
    2. a.name = 'Aaron' 修改 Person;类似于content['a']['v'] = 3 修改内部字典的方式。

    相当于您的第二个 sn-p 如下所示:

    a = 'Andy'
    b = 'Belle'
    
    k1_a = a
    
    a = 'Aaron'
    
    print(k1_a)  # output: Andy
    

    这一次,没有对象被修改。我们所做的只是覆盖a 变量的值,这正是content['a'] = 3 覆盖您的dict 中a 键的值的方式。


    如果您不希望内部 dicts 中的更改反映在其他 dicts 中,则必须使用 copy.deepcopy 复制它们:

    import copy
    
    content = {'a': {'v': 1}, 'b': {'v': 2}}
    d1 = {'k1': {}}
    d2 = {'k2': {}}
    d1['k1'].update(copy.deepcopy(content))
    print(d1)
    content['a']['v'] = 3
    content['b']['v'] = 4
    d2['k2'].update(copy.deepcopy(content))
    print(d2)
    print(d1)
    
    # output:
    # {'k1': {'a': {'v': 1}, 'b': {'v': 2}}}
    # {'k2': {'a': {'v': 3}, 'b': {'v': 4}}}
    # {'k1': {'a': {'v': 1}, 'b': {'v': 2}}}
    

    【讨论】:

      猜你喜欢
      • 2020-02-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-30
      相关资源
      最近更新 更多