我对浅表复制的工作方式有些困惑,我的理解是,当我们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
答案 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
不在乎(因为k
和i
是不是相同的参考文献)。 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都指向同一个列表对象。k = copy.copy(i)
是浅表副本,其中创建了列表对象的副本和嵌套引用的副本,但是内部的不可变对象未被复制。i[0] = 100
时,列表i中的引用指向值为100的新int对象,但k中的引用仍引用值为10的旧int对象。