python 中的浅拷贝
shallow copy in python
我对浅拷贝的工作原理有点困惑,我的理解是当我们这样做时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]
类似于赋值吗?当 i[0]
改变时 k[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
在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]
我正在这里创建一个 class MyInt,它只包含一个 int。
通过修改 class 的一个实例,两个列表 "change"。但是,当我替换列表条目时,列表现在不同了。
整数也是如此。你只是不能修改它们。
- 第一种情况
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对象。
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]
是对 相同 对象的引用)。
但是如果你将 a[0]
指向一个 new 对象(通过赋值,如 a[0] = [123]
),那么 b[0]
和 a[0]
不再指向同一个地方。
我对浅拷贝的工作原理有点困惑,我的理解是当我们这样做时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]
类似于赋值吗?当 i[0]
改变时 k[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
在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]
我正在这里创建一个 class MyInt,它只包含一个 int。 通过修改 class 的一个实例,两个列表 "change"。但是,当我替换列表条目时,列表现在不同了。
整数也是如此。你只是不能修改它们。
- 第一种情况
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对象。
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]
是对 相同 对象的引用)。
但是如果你将 a[0]
指向一个 new 对象(通过赋值,如 a[0] = [123]
),那么 b[0]
和 a[0]
不再指向同一个地方。