InterviewQuestion06: 什么是赋值、深拷贝、浅拷贝?
Question
什么是赋值
、深拷贝
、浅拷贝
?
知识点详解
对象的赋值Assignment
、拷贝Copy
(深/浅拷贝)之间是有差异的,这也是面试经常遇到的问题,本文就通过一些例子来理解下它们的区别。
赋值·Assignment
所谓赋值
,就是将一个对象的地址赋值给一个变量,让变量指向该地址。
>>> a = ["知识星球-ID", 91155859, ["人人", "都是", "Pythonista"]]
>>> b = a
>>> print(id(a), id(b), sep='\n')
2705125761736
2705125761736
在Python中,用一个变量给另一个变量赋值,其实就是给当前内存中的对象增加一个“标签”而已。如上面的例子,通过使用内置函数 id() ,可以看出 a 和 b 指向内存中同一个对象,此时is比较时:
>>> a is b
True
浅拷贝·ShallowCopy
所谓浅拷贝
,是在另一块地址中创建一个新的变量或容器,但是容器内的元素的地址均是源对象的元素的地址的拷贝。也就是说新的容器中指向了旧的元素。(拷贝组合对象,不拷贝子对象)
常见的浅拷贝操作有以下三种方式:
- 切片[:]操作
>>> a = ["知识星球-ID", 91155859, ["人人", "都是", "Pythonista"]]
>>> b = a[:]
>>> print(id(a), id(b)) # a 和 b 身份不同
2103264174792 2103265400456
>>> for i, j in zip(a, b): # 从打印结果不难看出:它们包含的子对象身份相同
... print(id(i), id(j))
...
2103264511872 2103264511872
2103235528592 2103235528592
2103264174728 2103264174728
- 工厂函数,如list/dir/set
>>> a = ["知识星球-ID", 91155859, ["人人", "都是", "Pythonista"]]
>>> b = list(a)
>>> print(id(a), id(b)) # a 和 b 身份不同
2455220242184 2455213751624
>>> for i, j in zip(a, b): # 从打印结果不难看出:它们包含的子对象身份相同
... print(id(i), id(j))
...
2739939238872 2739939238872
2739939283632 2739939283632
2739927343624 2739927343624
- copy模块中的
copy
函数
>>> a = ["知识星球-ID", 91155859, ["人人", "都是", "Pythonista"]]
>>> b = copy.copy(a)
>>> print(id(a), id(b)) # a 和 b 身份不同
2216345428872 2216345585544
>>> for i, j in zip(a, b): # 从打印结果不难看出:它们包含的子对象身份相同
... print(id(i), id(j))
...
2216343799680 2216343799680
2216343259024 2216343259024
2216345430280 2216345430280
从上面的三个例子可以明显的看出来,通过 a 浅拷贝得到的 b,a 和 b 指向内存中不同的list
对象,但它们的元素却指向相同的str
、int
、list
子对象。这就是浅拷贝!
深拷贝·DeepCopy
所谓“深拷贝”,是在另一块地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的,仅仅是值相同而已,是完全的副本。深拷贝出来的对象与原对象没有任何关联。
深拷贝只有一种方式:copy模块中的deepcopy
函数。
>>> import copy
>>> a = ["知识星球-ID", 91155859, ["人人", "都是", "Pythonista"]]
>>> b = copy.deepcopy(a)
>>> print(id(a), id(b))
1538766627848 1538766784456
>>> for i, j in zip(a, b):
... print(id(i), id(j))
...
1538736752512 1538736752512
1538736211856 1538736211856
1538766629128 1538766784520
看了上面的例子,有人可能会疑惑:为什么使用了深拷贝,a和b中元素的id还是一样呢?
其原因就是上述拷贝操作的对象中出现了str、int这种不可变对象。对于不可变对象,当需要一个新的对象时,Python可能会返回已经存在的某个类型和值都一致的对象的引用。但这种机制并不会影响 a 和 b 的相互独立性,因为当两个元素指向同一个不可变对象时,对其中一个赋值不会影响另外一个。
我们现在用一个仅包含了可变对象的列表,来再次演示“浅拷贝”与“深拷贝”的差异:
>>> import copy
>>> a = [[1, 2],[5, 6], [8, 9]]
>>> b = copy.copy(a) # 通过对 a 浅拷贝得到 b
>>> c = copy.deepcopy(a) # 通过对 a 深拷贝得到 c
>>> print(id(a), id(b)) # a 和 b 不同
3095031302024 3095031301704
>>> for i, j in zip(a, b): # 从打印结果不难看出:a和b的子对象是相同的
... print(id(i), id(j))
...
3095031146696 3095031146696
3095031145480 3095031145480
3095031301960 3095031301960
>>> print(id(a), id(c)) # a 和 c 不同
3095031302024 3095031301576
>>> for i, j in zip(a, c): # 从打印结果不难看出:a和c的子对象也是不同的
... print(id(i), id(j))
...
3095031146696 3095031301768
3095031145480 3095031301896
3095031301960 3095031302088
通过这个例子,你应该理解了浅拷贝与深拷贝的区别了吧😁
Answer
赋值
其实就是对象的引用(别名)。可以理解为:旧瓶装旧酒。
浅拷贝
拷贝父对象,不会拷贝对象的内部的子对象。可以理解为:新瓶装旧酒。
深拷贝
完全拷贝了父对象及其子对象。可以理解为:新瓶装新酒。
对于非容器类型(如数字、字符串、和其他’原子’类型的对象)没有拷贝这一说法
后记
浅拷贝和深拷贝的不同仅仅是对组合对象来说的,所谓的组合对象就是包含了其它对象的对象,如列表、类实例等等。而对于数字、字符串以及其它“原子”类型,没有拷贝一说,产生的都是原对象的引用。
"obj is copy.copy(obj)","obj is copy.deepcopy(obj)"
好了,以上就是本篇全部内容。
备注:本篇首发于知识星球「人人都是Pythonista」。