python中的浅表副本

时间:2018-10-13 18:42:13

标签: python list shallow-copy copy-assignment

我对浅表复制的工作方式有些困惑,我的理解是,当我们new_obj = copy.copy(mutable_obj)创建一个新对象时,它的元素仍然指向旧对象。

我感到困惑的例子-

## assignment
i = [1, 2, 3]
j = i
id(i[0]) == id (j[0])  # True
i[0] = 10
i  # [10, 2, 3]
j  # [10, 2, 3]

## shallow copy
k = copy.copy(i)
k   # [10, 2, 3]
id(i) == id(k)  # False (as these are two separate objects)
id(i[0]) == id (k[0])  # True (as the reference the same location, right?)
i[0] = 100
id(i[0]) == id (k[0])  # False (why did that value in that loc change?)
id(i[:]) == id (k[:])  # True  (why is this still true if an element just changed?)
i   # [100, 2, 3]
k   # [10, 2, 3]

在浅表复制中,k[0]是否仅指向i[0]类似于赋值? k[0]更改时i[0]是否应该更改?

为什么我希望它们是相同的,因为-

i = [1, 2, [3]]
k = copy(i)
i  # [1, 2, [3]]
k  # [1, 2, [3]]
i[2].append(4)
i  # [1, 2, [3, 4]]
k  # [1, 2, [3, 4]]
id(i[0]) == id (k[0])  # True
id(i[2]) == id (k[2])  # True
id(i[:]) == id (k[:])  # True

3 个答案:

答案 0 :(得分:2)

在Python中,所有事物都是对象。这包括整数。所有列表仅包含对对象的引用。替换列表中的元素并不意味着元素本身会发生变化。

考虑另一个示例:

class MyInt:
    def __init__(self, v):
        self.v = v
    def __repr__(self):
        return str(self.v)

>>> i = [MyInt(1), MyInt(2), MyInt(3)]
[1, 2, 3]
>>> j = i[:] # This achieves the same as copy.copy(i)

[1, 2, 3]
>>> j[0].v = 7
>>> j
[7, 2, 3]
>>> i
[7, 2, 3]

>>> i[0] = MyInt(1)
>>> i
[1, 2, 3]
>>> j
[7, 2, 3]

我在这里创建一个仅包含一个int的类MyInt。 通过修改类的实例,两个列表都列出了“更改”。但是,当我替换列表条目时,列表现在有所不同。

对于整数也是如此。您只是无法修改它们。

答案 1 :(得分:2)

  

id(i) == id(k) # False (as these are two separate objects)

正确。

  

id(i[0]) == id (k[0]) # True (as the reference the same location, right?)

正确。

  

i[0] = 100

     

id(i[0]) == id (k[0]) # False (why did that value in that loc change?)

此更改是因为您在上一行中进行了更改i[0] 指向10,但是您将其更改为指向100。因此,i[0]k[0]现在不再指向同一地点。

指针(引用)是一种方式10不知道该指向什么。 100也没有。它们只是内存中的位置。因此,如果您更改位置 i指向的第一个元素,则k不在乎(因为ki不是相同的参考文献)。 k的第一个元素仍然指向它一直指向的对象。

  

id(i[:]) == id (k[:]) # True (why is this still true if an element just changed?)

这有点微妙,但请注意:

>>> id([1,2,3,4,5]) == id([1,2,3])
True

>>> x = [1,2,3,4,5]
>>> y = [1,2,3]
>>> id(x) == id(y)
False

它与垃圾收集和ID的一些微妙有关,在这里得到了深入的解答:Unnamed Python objects have the same id

长话短说,当您说id([1,2,3,4,5]) == id([1,2,3])时,发生的第一件事就是我们创建了[1,2,3,4,5]。然后,通过调用id来获取它在内存中的位置。但是,[1,2,3,4,5]是匿名的,因此垃圾收集器立即对其进行回收。然后,我们创建另一个匿名对象[1,2,3],CPython恰好决定将其放在刚清理的位置。 [1,2,3]也将立即删除并清理。但是,如果您存储引用,GC不会妨碍您,那么引用就不同了。

可变项示例

如果重新分配可变对象,也会发生同样的事情。这是一个示例:

>>> import copy
>>> a = [ [1,2,3], [4,5,6], [7,8,9] ]
>>> b = copy.copy(a)
>>> a[0].append(123)
>>> b[0]
[1, 2, 3, 123]
>>> a
[[1, 2, 3, 123], [4, 5, 6], [7, 8, 9]]
>>> b
[[1, 2, 3, 123], [4, 5, 6], [7, 8, 9]]
>>> a[0] = [123]
>>> b[0]
[1, 2, 3, 123]
>>> a
[[123], [4, 5, 6], [7, 8, 9]]
>>> b
[[1, 2, 3, 123], [4, 5, 6], [7, 8, 9]]

区别在于您说a[0].append(123)时,我们正在修改a[0]指向的内容。碰巧是b[0]指向同一个对象(a[0]b[0]是对 same 对象的引用)。

但是,如果您将a[0]指向 new 对象(通过赋值,如a[0] = [123]),则b[0]a[0]不再指向同一地方。

答案 2 :(得分:0)

  • 在第一种情况下,j = i是一个赋值,j和i都指向同一个列表对象。
    当您更改列表对象的元素并打印i和j时,因为i和j指向相同的列表对象,它是元素而不是已更改的列表对象,因此两者将输出相同的输出。
  • 在第二种情况下,k = copy.copy(i)是浅表副本,其中创建了列表对象的副本和嵌套引用的副本,但是内部的不可变对象未被复制。
    浅表副本不会创建嵌套对象的副本,而是仅复制嵌套对象的引用。请参阅此https://www.programiz.com/python-programming/shallow-deep-copy
  • 因此,i和k具有指向同一不变对象的不同引用集。当您执行i[0] = 100时,列表i中的引用指向值为100的新int对象,但k中的引用仍引用值为10的旧int对象。