我知道Python中浅拷贝和深拷贝之间的区别,问题不在于何时使用其中一个。但是,我发现这个微不足道的例子非常有趣且不直观
from copy import deepcopy
a=0
b=deepcopy(a)
c=a
a+=1
print(a,b,c)
输出: 1 0 0
from copy import deepcopy
a=[0]
b=deepcopy(a)
c=a
a[0]+=1
print(a,b,c)
输出: [1]0 [1]
我想知道做出这种设计选择的原因,因为在我看来,这两个代码片段非常等效,但它们的输出完全不同。为了使自己更明确,我想知道为什么 = 在"原始"变量的情况下是深拷贝,而在"非原始"(但仍然是基本语言的一部分)变量的情况下是浅拷贝,如列表?我个人认为这种行为违反直觉 注意:我使用了python 3
这里的问题是可变性和不变性。
python中没有原始和非原始的东西,一切都是一种类型,有些只是内置的。
你需要了解python如何将数据存储在变量中。假设你来自C背景,你可以认为所有的python变量都是指针。
所有 python 变量都存储对变量值实际所在位置的引用。
内置的id
函数在某种程度上让我们查看变量值的实际存储位置。
>>> x = 12345678
>>> id(x)
1886797010128
>>> y = x
>>> id(y)
1886797010128
>>> y += 1
>>> y
12345679
>>> x
12345678
>>> id(y)
1886794729648
变量x
指向位置1886797010128
和位置1886797010128
保存10
的值。int
是 python 中的不可变类型,这意味着存储在位置中的数据1886797010128
无法更改。
当我们分配y = x
,y
现在也指向相同的地址,因为没有必要为相同的值分配更多内存。
更改y
时(请记住,int
是不可变的类型,其值无法更改),将在新位置1886794729648
中创建一个新的 int,y
现在指向新地址处的这个新 int 对象。
当您尝试更新保存不可变数据的变量的值时,也会发生同样的情况。
>>> id(x)
140707671077744
>>> x = 30
>>> id(x)
140707671078064
更改具有不可变数据的变量的值只会使变量指向具有更新值的新对象。
像list
这样的可变类型并非如此。
>>> a = [1, 2, 3]
>>> b = a
>>> id(a), id(b)
(1886794896456, 1886794896456)
>>> b.append(4)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3, 4]
>>>
a
是一个list
并且是可变的,使用append
等方法更改它实际上会改变地址1886794896456
的值。由于b
也指向同一地址,因此a
的值也会更新。
deepcopy
在不同的内存位置创建一个与其参数具有相同值的新对象,即传递给它的对象。
我想知道做出这种设计选择的原因
这仅仅是因为python是如何被设计为一种面向对象的语言的。在 java 对象中可以看到类似的行为。
我个人认为这种行为违反直觉
直觉来自实践。练习一种语言无助于其他语言的工作方式,不同的语言有不同的设计模式和约定,我认为应该付出一些努力来学习它们对于我们将要使用的语言是什么。
c = a
既不是浅拷贝也不是深拷贝,无论a
指的是什么。它甚至比这更浅 - 它只复制一个引用。在此赋值之后,c
和a
都保存对同一对象的引用。
在Python 中无法修改 int 的值。当你在 int 上使用+=
时,Python 会将一个新的 int 分配给你从哪里检索原始 int 的地方。
对于第一种情况,a += 1
重新分配a
变量,而b
和c
继续引用它们在赋值之前引用的整数。
对于第二种情况,a[0] += 1
重新分配a
引用的列表的单元格 0。b
继续引用副本,该副本保持不变,c
继续引用a
引用的同一列表。由于此列表已更改状态,因此可以通过c
变量看到更改。
顺便说一下,deepcopy
旨在生成深层副本,从某种意义上说,对返回值的(任意深度)修改不会修改参数,反之亦然。由于不可能在 Python 中修改 int 的值,因此 int 算作自身的(深度)副本,事实上,如果它的参数是 int,deepcopy
实现只是返回其参数。
>>> x = 1000
>>> copy.deepcopy(x) is x
True
在复制期间链接对象而不是基元是很常见的。
您的片段之间的区别在于,在第二个片段中,c 是列表 a 的副本,而列表是对象,因此它们是链接的。而 c 是第一个片段中原语的"副本",它没有链接。