Python中的深拷贝和浅拷贝
Python中的对象
Python中构造的任何数据类型都是一个对象。
每个对象都包含三个属性:
-
身份
-
类型
-
值
name = "Li"
print(id(name)) # 2770749417776
print(type(name)) # <class 'str'>
print(name) # Li
Python中的可变对象和不可变对象
可变对象:list,dict,set。指对象的值可变,身份不可变。
不可变对象:int,float,str,tuple。指对象的值和身份均不可变。如果新创建的对象关联到原来的变量名,就对象会被丢弃,垃圾回收期会在适当的实际回收这些对象。
a = [1, 2] # 可变对象
print(id(a)) # 2057274282376
a.append(3)
print(id(a)) # 2057274282376
# 可变对象的内容发生改变,但是身份不变
name = "Li" # 不可变对象
print(id(name)) # 2057279105328
name = "Lei"
print(id(name)) # 2057279105584
# 不可变对象的值发生改变,对应的身份也发生改变。
总的来说,Python中的不可变对象和C++中的常量比较接近,每个对象在内存中的地址相对固定的。
补充:
关于不可变对象int:
-
对于小整数[-5, 256],考虑到小整数可能频繁使用,出于性能考虑,python使用小整数对象缓冲池small_ints缓存了[-5, 257)之间的整数,该范围内的整数在Python系统中是共享的。小整数对象在程序启动过程中初始化,小整数对象的引用计数变量ob_refcnt(引用计数记录指向对象引用的个数,当变为0,则被释放)不会改变且永远>0。
-
对于超出了[-5, 257)之间的其他整数,Python同样提供了专门的缓冲池(通用整数对象的缓冲池),供这些所谓的大整数使用,避免每次使用的时候都要不断的malloc分配内存带来的效率损耗。
-
整数对象回收时,内存不会归还给系统,而是将对象的ob_type指向free_list,供新创建的对象使用。
关于可变对象:
-
Python中参数传递都是传递引用。但是对于可变类型和不可变类型有所区别:传入参数为可变类型,函数内部修改就会影响函数外部的变量;若传入参数为不可变类型,在函数内部修改改变量并不会影响函数外部的变量,因为修改的时候会先复制一份再修改?
Python中的浅拷贝和深拷贝
直接赋值
Python中直接赋值得到的是对象的引用。
浅拷贝
拷贝父对象,不会拷贝对象的内部子对象。
深拷贝
完全拷贝父对象及其子对象。因此原对象的任何改变都不回对拷贝对象有任何影响,相当于重新开辟内存,将原对象的数据拷贝进去。
-
对于不可变对象,赋值、浅拷贝和深拷贝后均指向同一个地址,拷贝前后不会互相影响。
-
对于可变对象:
-
赋值就是引用,变量之间会相互影响;
-
浅拷贝得到的新对象的地址和原对象不同,但是新对象里面的可变对象(如列表)的地址和原对象里的可变对象的地址是相同的。也就是说浅拷贝它拷贝的是浅层次的数据结构(不可变对象),对象里的可变对象作为深层次的数据结构并没有被拷贝到新地址里面去,而是和原对象里的可变对象指向同一个地址,所以在新对象或原对象里对这个可变对象做修改时,两个对象是同时改变的。
-
深拷贝会另外开辟一个内存空间,拷贝原对象,所以原对象做任何的改变都不会影响深拷贝后的对象。
-
实现方式:
-
浅拷贝在Python中有多种方式实现,如copy模块的copy函数、对象的copy函数、工厂方法、切片等;大多数情况下,编写程序时都是使用浅拷贝,除非有特定的需求。浅拷贝的优点:拷贝速度快,占用空间少,拷贝效率高。
-
深拷贝一般通过copy模块的copy.deepcopy实现。