在python中,赋值、浅拷贝和深拷贝,从最上层看,都可以完成将变量a的值赋予变量b。但是,三者的底层原理却是各有不同。面对不同的业务场景,三者之间并不是等价的,只有彻底搞清楚了其底层实现原理,才能做出正确的选择。
赋值
赋值就是简单的对象引用
a = [2, ['hello',3]]
b = a
在上面的代码中,a 和 b 是一样的,他们指向同一片内存,b 不过是a 的别名,是引用。使用 is方法去判断a和b,会返回 True,证明他们指向的地址相同,内容相同,还可以使用id()函数来查看两个列表的地址是否相同。
赋值操作(包括对象作为参数、返回值),在底层是不会开辟新的内存空间,它只是复制了对象的引用。也就是说除了 b 这个名字之外,没有其他的内存开销。两者本质上同一份数据。修改了 a,也就影响了b,同理,修改了b,也就影响了 a。
一句话总结:赋值运算符为整体引用的拷贝,指向的内存地址不发生变化
浅拷贝
浅拷贝(shallow copy)会创建新对象,其内容非原对象本身的引用,而是原对象内第一层对象的引用。浅拷贝有三种形式:切片操作、工厂函数、copy 模块中的 copy 函数。
# 切片操作
b = a[:] 或者 b = [x for x in a];
# 工厂函数
b = list(a);
# copy 函数
b = copy.copy(a);
浅拷贝产生的列表 b 不再是列表 a 了,使用 is 判断可以发现他们不是同一个对象,使用id查看,他们也不指向同一片内存空间。但是当我们使用 id(x) for x in a 和id(x) for x inb来查看a和b中元素的地址时,可以看到二者包含的元素的地址是相同的。
也就是说,a和b最外层的修改是各自独立的;但是a和b内部可变元素的修改是同步的。比如说
a = [1,[1,2],2]
b = a
# 此时
a[1].append(3)
print(b[1])
# [1, 2, 3]
这是因为,我们修改了嵌套的 list,修改外层元素,会修改它的引用,让它们指向别的位置,修改嵌套列表中的元素,列表的地址并未发生变化,指向的都是用一个位置。
一句话总结:浅拷贝为一层个体的引用拷贝
深拷贝
深拷贝(deep copy)只有一种形式,copy 模块中的 deepcopy()函数
深拷贝只有一种形式,copy 模块中的 deepcopy()函数。 深拷贝和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。因此,它的时间和空间开销要高。 同样的对列表 a,如果使用 b = copy.deepcopy(a),再修改列表b 将不会影响到列表a,即使嵌套的列表具有更深的层次,也不会产生任何影响,因为深拷贝拷贝出来的对象根本就是一个全新的对象,不再与原来的对象有任何的关联。
一句话总结:深拷贝为递归到最底层不可变元素的引用的拷贝