Python常见面试题001-005
参考资料
https://github.com/taizilongxu/interview_python
https://github.com/hantmac/Python-Interview-Customs-Collection
https://github.com/kenwoodjw/python_interview_question
有些来自上面(但我也做了自己的补充),有些来自网络或书籍
本文不准备写编程题,偏重于理论一些。你要的话去刷leetcode就是了。
倒序描述,限于篇幅,可能要连载
005. 说说你对浅拷贝、深拷贝的理解
- 浅拷贝 shallow copy
- 深拷贝 deep copy
浅拷贝
-
第一次见浅拷贝是在
>>> help(list.copy) Help on method_descriptor: copy(self, /) Return a shallow copy of the list.
-
list列表的copy方法,那浅拷贝到底是怎样的行为表现呢?
-
示例代码
list1 = [1,2,3] list2 = list1.copy() list1.append(4) print(list2) # [1,2,3] list1改变了,list2没变 , 浅拷贝是互相不会影响的
-
来看这段
list1 = [1,2,3] list2 = list1 list1.append(4) print(list2) # [1,2,3,4] ,赋值是引用,同时指向了一个内存区域,现在你改变了内存的信息,自然一起改变了
-
在第一个例子中
list1 = [1,2,3] list2 = list1.copy() print(id(list1)) print(id(list2)) # 显然2个id的值是不一样的,所以改变了你,并不会改变我 print(list1 == list2) # True print(list1 is list2) # False
- 来看看其他的浅拷贝方式
切片
-
可变序列的切片创建了一个浅拷贝,不可变序列的切片创建了一个引用
list1 = [1,2,3] list2 = list1[:] list1.append(4) print(list2) # [1,2,3] 跟list1不一样 print(list2 is list1) # False tuple1 = (1,2,3) tuple2 = tuple1[:] print(tuple1 is tuple2) # True
- 浅拷贝,是指重新分配一块内存,创建一个新的对象,里 面的元素是原对象中子对象的引用。因此,如果原对象中的元素不可变,那倒无所谓;但如果元 素可变,浅拷贝通常会带来一些副作用
构造器
-
常见的浅拷贝的方法,是使用数据类型本身的构造器
set1 = {1,2,3} set2 = set(set1) print(set2 is set1) # False
copy.copy()
-
copy.copy则可以用于任意的数据类型的浅拷贝
>>> from copy import copy >>> help(copy) Help on function copy in module copy: copy(x) Shallow copy operation on arbitrary Python objects. See the module's __doc__ string for more info.
-
示例代码
dict1 = {"name":"wuxianfeng","age":18} from copy import copy dict2 = copy(dict1) print(dict2 is dict1) dict1['height'] = 180 print(dict2)
特殊情况
-
来看下面的这些例子
l1 = [[1, 2], (30, 40)] l2 = list(l1) l1.append(100) l1[0].append(3) print(l2) # 请问此时l2是什么?
-
按照前面的理解,list构造器会产生浅拷贝
print(l2 is l1) # False # 说明这是2个不同的对象
-
按理说l1的变化不会影响到l2
-
但事实是影响了
[[1, 2, 3], (30, 40)] # 第四行代码发生作用了
-
仔细看会发现,line3没有改变,line4改变了
-
再看一个操作
# 承上 l1[1] +=(50,60) print(l2) # 还是[[1, 2, 3], (30, 40)] 但l1肯定是变了的
-
完整的解释
l1 = [[1, 2], (30, 40)] l2 = list(l1) # 对l1执行浅拷贝,赋予l2。 # 因为浅拷贝里的元素是对原对象元素的引用,因此l2中的元素和l1中的元素指向同一个列表和元组对象 l1.append(100) # 这个操作不会对l2产生任何影响,因为l2和l1作为整体是两个不同的对象,并不共享内存地址 l1[0].append(3) # l1[0] 是[1, 2] # 因为l1和l2的[1, 2]是同一个对象,不信? # print(id(l1[0])) # print(id(l2[0])) # print(l1[0] is l2[0]) # True # 所以l1[0].append(3) 这步会对l1和l2的[1,2]都追加一个元素3 l1[1] +=(50,60) # 最麻烦的是这个 # l1[1] 是(30,40) ,是个元组 # l2[1] 的确是同一个对象 # l1[1] +=(50,60) 会创建一个新的,注意是新的!元组 # 但l2不会
-
你可以这样验证
print('l1的元组的id',id(l1[1]),'l1的元组的id',id(l2[1])) l1[1] +=(50,60) print('l1的元组的id',id(l1[1]),'l1的元组的id',id(l2[1])) # 输出参考 l1的元组的id 2411404337920 l1的元组的id 2411404337920 l1的元组的id 2411404749456 l1的元组的id 2411404337920 # 是的,l1中的元组已经变了,l2的没有改变
-
至此你应该发现了浅拷贝的一些副作用
深拷贝
-
深拷贝来自copy模块的deepcopy方法
-
同样看上面的例子
from copy import deepcopy l1 = [[1, 2], (30, 40)] l2 = deepcopy(l1) l1.append(100) l1[0].append(3) l1[1] +=(50,60) print(l2) # [[1, 2], (30, 40)] # 就是你复制的时候的样子
-
好像deepcopy很完美?
-
复制的时候的确不希望互相影响
-
但deepcopy有它的弊端:如果被拷贝对象中存在指向自身的引 用,那么程序很容易陷入无限循环
官方解释
-
Python 的赋值语句不复制对象,而是创建目标和对象的绑定关系
-
对于自身可变,或包含可变项的集合,有时要生成副本用于改变操作,而不必改变原始对象。本模块提供了通用的浅层复制和深层复制操作
- copy.copy(x) 浅