前言
在Python内置的copy模块中定义了copy与deepcopy函数, 可以实现对任意Python对象的复制。如果需要自定义控制Python对象的复制行为,可以自定义__copy__
与__deepcopy__
方法, 分别对应浅拷贝与深拷贝。
浅拷贝与深拷贝的定义
-
copy.copy(x)
- 返回对象x的浅拷贝备份。浅拷贝会先生成一个容器对象,然后将原始对象中包含的“对象引用”插入到新容器中(A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original)。
-
copy.deepcopy(x)
- 返回对象的深拷贝。深拷贝会先生成一个容器对象,然后将原始对象中包含的元素的“副本”插入新容器中(A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original)。
浅拷贝拷贝的是“对象引用”, 深拷贝拷贝的是“元素副本”。元素副本是什么意思?可以通过下列实例验证:
import copy
x = [1, [2, 3], (4, 5)]
y = copy.copy(x)
z = copy.deepcopy(x)
# 对于嵌套型可变类型容器,深拷贝浅拷贝都会生成一个新容器
print(id(x), id(y), id(z)) # 2901579592640 2901579610816 2901579611136
# 第一个元素是列表,浅拷贝复制的是对象引用,深拷贝会复制值并新建一个同类型的对象
print(id(x[1]), id(y[1]), id(z[1])) # 2901579568960 2901579568960 2901579611520
# 第二个元素是元组,深拷贝与浅拷贝都指向同一对象
print(id(x[2]), id(y[2]), id(z[2])) # 2901579567296 2901579567296 2901579567296
从上面实例可以看出,深拷贝与浅拷贝的最大影响因素应该是对象的类型:“可变类型还是不可变类型”。对于不可变对象,浅复制与深复制作用相同;但是对于“可变类型”,深复制与浅复制差异就非常大,对于浅拷贝,备份与母本包含的是同一对象的引用,当被引用的对象发生改变时,这种改变会同时反映到母本与浅复制对象上。 而深拷贝会复制母本的值并新建一个同类型的对象,不存在引用同一对象的引起的问题。
常见的类型构造方法
x = [1, 2, 3]; y = list(x); print(id(x), id(y), x == y, id(x) == id(y)) # 2901579599424 2901579609856 True False
x = (1, 2, 3); y = tuple(x); print(id(x), id(y), x == y, id(x) == id(y)) # 2901579538624 2901579538624 True True
x = "123"; y = str(x); print(id(x), id(y), x == y, id(x) == id(y)) # 2901575533296 2901575533296 True True
x = {"a": 1}; y = dict(x); print(id(x), id(y), x == y, id(x) == id(y)) # 2901579611904 2901580800640 True False
x = set([1, 2, 3]); y = set(x); print(id(x), id(y), x == y, id(x) == id(y)) # 2901583446080 2901583446304 True False
对于不可变类型str
,tuple
,类型构造方法没有创建新对象;对于可变类型list
,dict
,set
,类型构造方法会创建新对象。
亲密字符串实例
- 采用shallow copy时,会得到错误的结果
import copy
def buddyStrings(A, B):
"""
亲密字符串
:type A: str
:type B: str
:rtype: bool
"""
length = len(A)
res = []
temp = list(A)
for i in range(length):
for j in range(i+1, length):
# 采用shallow copy的方式将temp的值赋给变量 “C"
C = temp
C[j], C[i] = C[i], C[j]
res.append(''.join(C) == B)
if sum(res) >=1:
return True
else:
return False
buddyStrings("aaaaaaabc", "aaaaaaacb")
# 结果返回Fales,但应该是True
- 采用deep copy的方式得到正确的结果
import copy
def buddyStrings(A, B):
length = len(A)
res = []
temp = list(A)
for i in range(length):
for j in range(i+1, length):
# 采用deep copy的方式将temp的值赋给变量 “C"
C = copy.deepcopy(temp)
C[j], C[i] = C[i], C[j]
res.append(''.join(C) == B)
if sum(res) >=1:
return True
else:
return False
buddyStrings("aaaaaaabc", "aaaaaaacb")
# 结果返回是True
总结
- 深拷贝与浅拷贝的重要影响因素是对象的类型——可变类型与不可变类型。
- 明确对象的深拷贝与浅拷贝结果,能查看
__copy__
与__deepcopy__
源码定义是最好的。 - 另外补充:Python中的赋值语句从“等号右侧”开始执行,右侧表达式执行完成之后,会将“等号左侧”的变量标识符绑定到表达式返回对象上。如果为同一对象绑定多个“标识符”,则可以将其它的标识符看作“别名”。需要注意的是,在赋值语句中,是没有复制右侧的对象的操作,只是将左侧的标识符绑定到右侧的对象上(Assignment statements in Python do not copy objects, they create bindings between a target and an object)。
参考资料
- 《流程的Python》第8章——对象引用、可变性与垃圾回收
- https://docs.python.org/3.7/library/copy.html