4

我对浅拷贝的工作方式有点困惑,我的理解是当我们这样做时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
4

3 回答 3

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?)

它发生了变化,因为您在上一行中对其进行了更改i[0] 指向10,但您将其更改为指向100。因此,i[0]现在k[0]不再指向同一个点。

指针(引用)是一种方式10不知道是什么指向它。也没有100。它们只是内存中的位置。因此,如果您更改第一个元素指向的位置 ,则不在乎(因为和不是同一个引用)。的第一个元素仍然指向它一直指向的东西。ikkik

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 的一些微妙之处有关,在这里深入回答:未命名的 Python 对象具有相同的 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]对象的引用)。

但是如果你指向a[0]一个对象(通过赋值,如中a[0] = [123]),那么b[0]a[0]不再指向同一个地方。

于 2018-10-13T19:25:04.603 回答
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]

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

整数也是如此。你只是不能修改它们。

于 2018-10-13T19:05:18.620 回答
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 对象。
于 2018-10-13T19:09:24.060 回答