一、赋值
前面说过,一个对象有 id、type、value 三个要素,变量名通过引用 id 的方式去找到一个对象(类似于文件名通过 inode 找到具体数据块,只有 id 能唯一标识一个对象)。
而用一个变量赋值给令一个变量,就相当于这两个变量共享了这个引用(有点像硬链接)
如果删除掉 a 之后,并不会影响 b 的引用
在不可变数据类型中这是没问题的,变量每次重新赋值都会将其引用改成新对象的地址
但是在可变数据类型中,这就会出问题
原因是 a 和 b 共享了同一个列表的引用,而列表是可变类型对象,改变列表中的值列表的 id 并不会改变(列表的 value 事实上就是一个个对象 id 的引用,所以对应 value 的值会变)
拷贝概念的引入就是针对可变对象的共享引用潜在的副作用而提出的
二、浅拷贝
如果不想改变 b 的值,就要使用 copy 模块或使用切片
可以看到,由于 a、b、c 三个变量引用了不同的列表对象,对列表中的元素改变并不会影响其他两个列表
但这还是有问题的
原因是虽然使用浅拷贝时在内存中新开辟了一块空间存放一个新列表,但列表中的 value 并没有变
而这三个列表最后一个元素又是一个列表,是可变类型的对象,改变其元素的值并不会改变其地址
浅拷贝就相当于重新为列表中的元素做了一个”壳“,列表中存放的元素(value)并没有发生变化,变的是列表的 id
要解决这种问题,就要使用深拷贝
三、深拷贝
在深拷贝中,不仅列表的这个”壳“会变,列表中要是有其他可变类型的数据,它们也会被重新开辟一块内存空间来存放
可以看到,改变 a[-1] 的值并不会改变影响深拷贝出来的列表,而切片出来的列表被改变了
可以看到切片前后元素的 id 并没有发生变化,这就可以说明:切片和浅拷贝是等效的
事实上,要理解深拷贝,应该是这样的:深拷贝会递归的将列表中每一个元素都重新生成一次
但是在 Python 中对这个过程进行了优化,如果列表中的元素是不可变类型的话,深拷贝会将这些对象复用,并不会重新为它们开辟一块空间赋值(大概是因为对引用不可变类型的对象赋值会更改其引用),就成了下图
四、总结
- 赋值是对引用的共享
- 浅拷贝和切片的效果相同,浅拷贝一个可变类型对象会开辟一个新的空间,但对象中的值是不会发生变化的
- 深拷贝会递归的对可变类型对象中的所有元素继续深拷贝
- Python 中对拷贝做了优化,不可改变类型对象的拷贝并不会开辟新空间,仍是共享引用