1、浅拷贝(Shallow Copy)
浅拷贝是对于一个对象的顶层拷贝。创建一个新对象,但只复制原对象的第一层元素(顶层容器),而不复制嵌套的子对象
2、深拷贝(Deep Copy)
深拷贝是对一个对象所有层次的拷贝(递归)。创建一个完全独立的新对象,递归复制所有层级的元素,包括嵌套的子对象。
深拷贝的实现机制
当执行 copy.deepcopy()时:
- 首先检查对象是否已经在备忘录(memo)字典中(防止循环引用)
- 然后检查对象类型:
- 如果是不可变类型,直接返回原对象
- 如果是可变类型,创建新对象并递归复制内容
- 对于自定义类,会调用 __deepcopy__()方法(如果存在)
下面分别从赋值,浅拷贝,深拷贝三个示例代码来说明差异:
赋值示例代码如下:
#!/usr/bin/python3 # 赋值操作(=) a = [1,2,[3,4]] b = a # 赋值后两个对象的内存地址都一样,只是创建新引用,不复制数据,说明b是a的引用,b改变会同样影响a print("&a=0x%x,&b=0x%x" %(id(a),id(b))) # &a=0x731bd8350dc0,&b=0x731bd8350dc0 # &a[0]=0x731bd8cc40f0,&b[0]=0x731bd8cc40f0 print("&a[0]=0x%x,&b[0]=0x%x" %(id(a[0]),id(b[0]))) # &a[1]=0x731bd8cc4110,&b[1]=0x731bd8cc4110 print("&a[1]=0x%x,&b[1]=0x%x" %(id(a[1]),id(b[1]))) # &a[2]=0x731bd8db86c0,&b[2]=0x731bd8db86c0 print("&a[2]=0x%x,&b[2]=0x%x" %(id(a[2]),id(b[2]))) # &a[2][0]=0x731bd8cc4130,&b[2][0]=0x731bd8cc4130 print("&a[2][0]=0x%x,&b[2][0]=0x%x" %(id(a[2][0]),id(b[2][0]))) b[0] = 99 print(a) # [99, 2, [3, 4]] print(b) # [99, 2, [3, 4]] b[2][0] = 88 print(a) # [99, 2, [88, 4]] print(b) # [99, 2, [88, 4]]浅拷贝示例代码如下:
#!/usr/bin/python3 import copy # 浅拷贝创建一个新对象,但只复制原对象的第一层元素(顶层容器),而不复制嵌套的子对象 a = [1,2,[3,4]] b = copy.copy(a) # a,b内存地址不同,说明不是引用,是两个不同的对象 print("&a=0x%x,&b=0x%x" %(id(a),id(b))) # &a=0x7b0be03e44c0,&b=0x7b0be03e4480 # &a[0]=0x7b0be0e900f0,&b[0]=0x7b0be0e900f0,顶层元素内存地址一样(int为不可变类型,共享地址,所以地址一样,确实发生了拷贝动作) print("&a[0]=0x%x,&b[0]=0x%x" %(id(a[0]),id(b[0]))) # &a[1]=0x7b0be0e90110,&b[1]=0x7b0be0e90110 print("&a[1]=0x%x,&b[1]=0x%x" %(id(a[1]),id(b[1]))) # &a[2]=0x7b0be03e4440,&b[2]=0x7b0be03e4440,嵌套元素内存地址一样(列表为可变类型,说明嵌套元素确实是浅拷贝) # a中嵌套元素值的改变同样会影响b中嵌套元素值的内容 print("&a[2]=0x%x,&b[2]=0x%x" %(id(a[2]),id(b[2]))) # &a[2][0]=0x7b0be0e90130,&b[2][0]=0x7b0be0e90130,a[2][0]和b[2][0]内存地址一样 print("&a[2][0]=0x%x,&b[2][0]=0x%x" %(id(a[2][0]),id(b[2][0]))) # 修改顶层元素 b[0] = 99 print(a) # [1, 2, [3, 4]],a[0]值不变 print(b) # [99, 2, [3, 4]] # 修改嵌套元素 b[2][0] = 88 print(a) # [1, 2, [88, 4]],a[2][0]值发生变化,原对象被修改 print(b) # [99, 2, [88, 4]]深拷贝示例代码如下:
#!/usr/bin/python3 import copy # 深拷贝创建一个完全独立的新对象,递归复制所有层级的元素,包括嵌套的子对象 a = [1,2,[3,4]] b = copy.deepcopy(a) # 内存地址不同,说明不是引用,是两个不同的对象 print("&a=0x%x,&b=0x%x" %(id(a),id(b))) # &a=0x768e7e8e02c0,&b=0x768e7e8e0280 # &a[0]=0x768e7eb000f0,&b[0]=0x768e7eb000f0,顶层元素内存地址一样(int为不可变类型,共享地址,所以地址一样,确实发生了拷贝动作) print("&a[0]=0x%x,&b[0]=0x%x" %(id(a[0]),id(b[0]))) # &a[2]=0x7feede8c43c0,&b[2]=0x7feede8c5a80,嵌套元素内存地址不一样,说明嵌套列表是两个不同的对象,发生了拷贝动作(列表为可变类型) print("&a[2]=0x%x,&b[2]=0x%x" %(id(a[2]),id(b[2]))) # &a[2][0]=0x768e7eb00130,&b[2][0]=0x768e7eb00130 print("&a[2][0]=0x%x,&b[2][0]=0x%x" %(id(a[2][0]),id(b[2][0]))) # 修改顶层元素 b[0] = 77 print(a) # [1, 2, [3, 4]] print(b) # [77, 2, [3, 4]] # 修改底层元素 b[2][0] = 66 print(a) # [1, 2, [3, 4]] print(b) # [77, 2, [66, 4]]Q:为什么深拷贝后,对象内部某些类型的id()值还是一样?
A:在Python中,深拷贝后两个对象的id()值有时会相同,这看起来似乎违反了深拷贝的预期行为。但实际上这是由Python的对象模型和深拷贝的实现机制决定的。
核心原因:不可变对象的优化
Python对不可变对象(immutable objects)有特殊处理:
当深拷贝一个不可变对象时,Python会直接返回原始对象而不是创建新副本
因为不可变对象无法被修改,共享引用是安全的,这样可以节省内存和提高性能
常见不可变对象类型:
数字类型:int, float, complex
布尔值:bool
字符串:str
元组:tuple(当所有元素都是不可变时)
冻结集合:frozenset
字节串:bytes
示例代码如下:
#!/usr/bin/python3 import copy # 不可变对象(整数) a = 256 b = a # 赋值 print(a is b) # True print(id(a) == id(b)) # True b = 128 print(a,b) # 256 128 c = copy.deepcopy(a) # 深拷贝 print(c is a) # True print(id(c) == id(a)) # True c = 128 print(a,b,c) # 256 128 128 # 不可变对象(字符串) s1 = "python" s2 = copy.deepcopy(s1) print(s1 is s2) # True print(id(s1) == id(s2)) # True s3 = s2.replace('t','a',1) print(s3 is s1) # False s4 = s1 print(s4 is s1) # True # 不可变对象(元组) t1 = (1,"a",(3,4)) t2 = copy.deepcopy(t1) print(t1 is t2) # True print(id(t1) == id(t2)) # True t3 = ([1,2],'b') #元组包含可变对象 t4 = copy.deepcopy(t3) print(t3 is t4) # False print(id(t3) == id(t4)) # False print(id(t3[0]) == id(t4[0])) # False # 可变对象(列表) l1 = [1,2,[3,4]] l2 = copy.deepcopy(l1) print(l1 is l2) # False print(id(l1) == id(l2)) # False print(id(l1[2]) == id(l2[2])) # False