搞定面试之图解Python深拷贝浅拷贝
首先了解一些基本概念
「在Python中一切皆对象」,任何对象都有三个属性:唯一标识、类型、值。
例如一个字符串:
lst= "hello python!"
# 内存地址,唯一标识
print(id(lst))
# 存储类型
print(type(lst))
# 变量值
print(lst)
# 1914884325552
# <class 'str'>
# hello python!
id() ,是Python的一个内置函数,返回对象的唯一标识,用于获取对象的内存地址
「引用」:在 Python 程序中,每个对象都会在内存中申请开辟一块空间来保存该对象,该对象在内存中所在位置的地址被称为引用,使用变量名进行指代。
「赋值」:简单来说就是对于同一个对象,增加一个别名。原理就是将一个对象的地址赋值给一个变量,使得变量指向该内存地址。
a=1
b=a
拷贝的作用:
为了解决数据传递后被修改的问题,就需要拷贝一份副本,即使副本被修改,也不会影响原始数据 。
「不可变类型(字符串、数值型、布尔值)的深浅拷贝」
# 不可变对象的浅拷贝,以数值为例
import copy
a=5
# 浅拷贝
b=copy.copy(a)
print(id(a))
print(id(b))
# 修改a的值
a=10
print(id(a))
print(id(b))
# 140732266387232
# 140732266387232
# 140732266387392
# 140732266387232
# 不可变对象的深拷贝,以数值为例
import copy
a=5
# 深拷贝
b=copy.deepcopy(a)
print(id(a))
print(id(b))
# 修改a的值
a=10
print(id(a))
print(id(b))
# 140732266387232
# 140732266387232
# 140732266387392
# 140732266387232
「综上,不可变对象的拷贝,浅拷贝和深拷贝一样,对象的引用(内存地址)没有发生变化。」
「可变对象的深浅拷贝」
「1.可变对象(列表、字典、集合)的浅拷贝」
#这里以嵌套列表为例
import copy
lsta = [1, 2, 3]
lstb = [1, 2, [3, 4, 5]]
lsta1 = copy.copy(lsta) # 非嵌套浅拷贝
lstb1 = copy.copy(lstb) # 嵌套列表浅拷贝
print(id(lsta))
print(id(lsta1))
print(id(lsta[0]), id(lsta[1]),id(lsta[2]))
print(id(lsta1[0]), id(lsta1[1]), id(lsta1[2]))
# 1373054849088
# 1373054934336
# 140732266387104 140732266387136 140732266387168
# 140732266387104 140732266387136 140732266387168
print(id(lstb))
print(id(lstb1))
print(id(lstb[0]), id(lstb[2]), id(lstb[2][0]))
print(id(lstb1[0]), id(lstb1[2]), id(lstb1[2][0]))
# 2735667449472
# 2735667458368
# 140732266387104 2735667382912 140732266387168
# 140732266387104 2735667382912 140732266387168
浅拷贝在拷贝时,只会copy一层,在内存中开辟一个空间,存放这个copy的列表。更深的层次并没有copy,即第二层用的都是同一个内存。
「2.可变对象(列表、字典、集合)的深拷贝」
import copy
lsta = [1, 2, 3]
lstb = [1, 2, [3, 4, 5]]
lsta1 = copy.deepcopy(lsta) # 非嵌套深拷贝
lstb1 = copy.deepcopy(lstb) # 嵌套列表深拷贝
print(id(lsta))
print(id(lsta1))
print(id(lsta[0]), id(lsta[1]),id(lsta[2]))
print(id(lsta1[0]), id(lsta1[1]), id(lsta1[2]))
# 2310617518336
# 2310617599488
# 140732194690720 140732194690752 140732194690784
# 140732194690720 140732194690752 140732194690784
print(id(lstb))
print(id(lstb1))
print(id(lstb[0]), id(lstb[2]), id(lstb[2][0]))
print(id(lstb1[0]), id(lstb1[2]), id(lstb1[2][0]))
# 2310617599744
# 2310617608704
# 140732194690720 2310617532800 140732194690784
# 140732194690720 2310617608640 140732194690784
综上,深拷贝时,会逐层进行拷贝,遇到可变类型,就开辟一块内存复制下来,遇到不可变类型就沿用之前的引用。
因为不可变数据修改会从新开辟新的空间,所以,深拷贝数据之间的修改都不会相互影响。
总结:
- 浅拷贝花费时间少,占用内存少,只拷贝顶层数据,拷贝效率高。
- 对不可变对象拷贝时,浅拷贝和深拷贝的作用是一致的,不开辟新空间,相当于赋值操作。
- 可变对象浅拷贝时,只拷贝第一层中的引用,如果元素是可变对象,并且被修改,那么拷贝的对象也会发生变化。
- 可变对象深拷贝时,会逐层进行拷贝,遇到可变类型,就开辟一块内存复制下来。
「补充一个常考知识点」
「is 和 == 的区别」
- is:「比较两个对象的引用(id值)是否相同,即是否指向同一个内存地址」
- == : 「python中的比较操作符,比较两个对象的值是否相等。」
a=1
b=1
print(a == b)
print(a is b)
print(id(a))
print(id(b))
# True
# True
# 140732603438752
# 140732603438752
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)
print(a is b)
print(id(a))
print(id(b))
# True
# False
# 2926980497856
# 2926981792128
只要 a 和 b 的值相等,a == b 就会返回True,而只有 id(a) 和 id(b) 相等时,a is b 才返回 True。
因为只需要判断两个对象的id是否相同,所以is比较的效率更高,而==默认会调用对象的 __eq__方法,遍历变量中的所有元素是否相同,效率较低。