【python】面试高频:浅拷贝 vs 深拷贝、'==' vs 'is'
关于python在面试中会被问到哪些知识点,其实这主要取决于面试官。
额,别拿刀先,马上扯正题。
从我遇到的问题当中来看,除了有少数的常见问题外,更多的还是平时需要你额外拓展学习了解的知识点:
- 常见问题:比如,可变/不可变数据类型、json的序列化和反序列化、对象的引用/作用域,字典的常用操作等等。
- 额外知识:比如,python的内存管理机制、浅拷贝 vs 深拷贝、'==' vs 'is'等等。
面试官为什么会问一些额外知识,我觉得还是在于考察候选人对语言了解的深度如何。如果你能静下心来去看一些python语言的书籍(我是属于没静下心的),回答这些问题基本上都不是什么问题。
我现在也在翻阅几年前就买来的python书籍,有类似这种值得分享的知识点,在后续会一一带来。
今天就先聊聊,我觉得面试出现频率最高的:浅拷贝 vs 深拷贝、'==' vs 'is'
一、'==' vs 'is'
为什么先聊这个,因为在后面的拷贝知识点中,会用到对象的比较。
首先,先抛出知识点:
- '=='操作符比较对象之间的值是否相等。
- 'is'操作符比较的是对象的身份标识是否相等,即它们是否是同一个对象,是否指向同一个内存地址。
注意
==
操作符是用来比较对象之间的值是否相等。
看示例(命令行交互下运行python):
λ python
>>> a = 10
>>> b = 10
>>> a == b
True
>>> id(a)
2407440149072
>>> id(b)
2407440149072
>>> a is b
True
>>>
- 可以看到上面有2个变量 a 和 b,它们的值都是 10。
- 在 Python 中,每个对象的身份标识,都能通过函数 id(object) 获得,所以 使用
is
,相当于比较对象之间的 ID 是否相等。
在交互运行的结果中,可以看到:
- a == b,结果是 True。
- a is b,结果也是 True。
嗯?那这俩效果不是一样嘛?
分析一下:
- 首先 Python 会为 10 这个值开辟一块内存。
- 变量 a 和 b 同时指向这块内存区域。
所以,因此 a 和 b 的值相等,id 也相等。
但是,对于整型数字,a is b
是True
,只适用于 -5 到 256 范围内的数字。
现在用 257 试下:
>>> a = 257
>>> b = 257
>>> a is b
False
看到结果为 False。
原因在于,Python 出于对性能优化的考虑,内部会对 -5 到 256 的整型维持一个数组,起到一个缓存的作用。当你每次试图创建一个 -5 到 256 范围内的整型数字时,Python 都会从这个数组中返回相对应的引用,而不是重新开辟一块新的内存空间。
但是,如果整型数字超过了这个范围,比如上述例子中的 257,Python 则会为两个 257 开辟两块内存区域,因此 a 和 b 的 ID 不一样,a is b就会返回 False 了。
通常情况
我们在使用中,还是用==
更多,因为我们更关心两个变量的值,而不是它们内部的存储地址。
不过当判断这个变量是不是为None的时候,通常会使用is
了:
if a is None:
...
if a is not None:
...
二、浅拷贝 vs 深拷贝
深浅先不说,拷贝大家应该都会用到过。
>>> a = [1, 2, 3]
>>> b = list(a)
>>> b
[1, 2, 3]
>>> a == b
True
>>> a is b
False
这里的 b 就是 a 的浅拷贝了,当然你也可以用列表的切片来处理:b = a[:]
。
还可以用python 内部提供的函数copy
来处理。
>>> import copy
>>> a = [1, 2, 3]
>>> b = copy.copy(a)
>>> b
[1, 2, 3]
>>> a is b
False
1. 浅拷贝
浅拷贝:是指重新分配一块内存,创建一个新的对象,里面的元素是原对象中子对象的引用。
怎么讲?
来看一个新的示例:
>>> import copy
>>> a = [1, 2, 3, 4, ['a', 'b']]
>>> b = copy.copy(a)
>>> a
[1, 2, 3, 4, ['a', 'b']]
>>> b
[1, 2, 3, 4, ['a', 'b']]
>>> a is b
False
- a 是一个列表,列表里面又存放了 5 个元素,其中第5个元素也是一个列表 ['a', 'b']。
- b 是 a 的浅拷贝,所以打印出来 a 和 b 的值都相等。
- a is b 是 False,说明是2个不同的对象。
注意
“新的对象,里面的元素是原对象中子对象的引用”,这里的子对象的引用,是怎么回事?
现在我去修改 a,增加一个元素,应该是不会影响到 b。
>>> a.append(5)
>>> a
[1, 2, 3, 4, ['a', 'b'], 5]
>>> b
[1, 2, 3, 4, ['a', 'b']]
但是,如果我去修改列表 a 中的子对象 ['a', 'b'],增加一个元素 'c',再去看下 a,b 分别是什么:
>>> a[4].append('c')
>>> a
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> b
[1, 2, 3, 4, ['a', 'b', 'c']]
可以看到,b 受到影响了,b 的子对象列表也从原来的 ['a', 'b'] 变成了现在的 ['a', 'b', 'c']。
所以这也就解释了上述的,“浅拷贝,是指重新分配一块内存,创建一个新的对象,里面的元素是原对象中子对象的引用”
这也是浅拷贝的问题所在,在日常使用过程中,需要注意的地方。
2. 深拷贝
看到这,聪明的你或许已经明白深拷贝的作用了。没错,就是解决了浅拷贝的问题,实现真正的全拷贝。
深拷贝:是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。
因此,新对象和原对象没有任何关联。
再重新演示下上面的示例,用深拷贝copy.deepcopy()
:
>>> import copy
>>> a = [1, 2, 3, 4, ['a', 'b']]
>>> b = copy.deepcopy(a)
>>> a
[1, 2, 3, 4, ['a', 'b']]
>>> b
[1, 2, 3, 4, ['a', 'b']]
>>> a[4].append('c')
>>> a
[1, 2, 3, 4, ['a', 'b', 'c']]
>>> b
[1, 2, 3, 4, ['a', 'b']]
用了深拷贝后,a 就算修改了子对象,b 也没有受到影响了。