我知道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
答案 0 :(得分:2)
这里的收获是可变性和不变性。
在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
和{{ 1}}现在指向新地址处的新int对象。
当您尝试更新保存不可变数据的变量的值时,也会发生同样的情况。
y
更改具有不变数据的变量的值只会使该变量指向具有更新后值的新对象。
>>> 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
在另一个内存位置创建一个新对象,该对象的值与其参数相同,即传递给它的对象。
我想知道做出此设计选择的原因
这仅仅是因为python如何设计为面向对象的语言。在Java对象中可以看到类似的行为。
我个人认为这种行为违反直觉
直觉来自实践。练习一种语言不能帮助其他语言工作,不同语言有不同的设计模式和约定,我认为应该花很多精力来了解它们对我们将要使用的语言的含义。
答案 1 :(得分:1)
c = a
既不是浅表副本也不是深表副本,无论a
指的是什么。它甚至比它还浅-仅复制参考。在分配后,c
和a
都拥有对同一对象的引用。
无法在Python中修改int的值。在int上使用+=
时,Python会将新int(对a的引用)分配给从中检索原始int的任何位置。
对于第一种情况,a += 1
重新分配了a
变量,而b
和c
继续引用了它们在分配之前所引用的int。
对于第二种情况,a[0] += 1
重新分配列表a
所引用的单元格0。 b
继续引用未更改的副本,并且c
继续引用a
引用的同一列表。由于此列表的状态已更改,因此可以通过c
变量看到更改。
顺便说一下,deepcopy
旨在产生一个深拷贝,这意味着对返回值的(任意深)修改不会修改自变量,反之亦然。由于无法在Python中修改int的值,因此int会算作其自身的(深层)副本,并且实际上,deepcopy
实现只是返回其参数(如果其参数为int)。>
>>> x = 1000
>>> copy.deepcopy(x) is x
True
答案 2 :(得分:-1)
在复制过程中而不是在原始数据之间链接对象是很平常的事情。
您的代码片段之间的区别在于,在第二个代码片段中,c是列表a的副本,而列表是对象,因此它们是链接的。而c是第一个代码段中原始数据的“副本”,而不会链接。