变量的赋值
在 Python 中,要创建一个列表 [1, 2, 3] 并赋值给变量 a 的语法是这样的:a = [1, 2, 3]。通常我们称 a 为 变量名,[1, 2, 3] 为 变量的值。
给一个变量赋值的操作实际上就是将一个变量名指向一个对象,a = [1, 2, 3] 就相当于将变量名 a 指向 [1, 2, 3] 这个列表对象。此时将变量 a 再赋值给变量 b:b = a,相当于将变量 b 也指向 [1, 2, 3] 列表。最终 a 和 b 指向的是同一个 [1, 2, 3] 列表。
1 | a = [1, 2, 3] |
在 Python 中可以通过 is 来比较两个变量是否为同一个对象,所以 a 和 b 本质上是同一个对象,只不过是这个对象的两个 名字 罢了。
此时,将列表 [4, 5, 6] 赋值给变量 a:a = [4, 5, 6],再次对变量 a 进行修改,会得到什么样的结果呢?
1 | a = [4, 5, 6] |
可以看到,将变量 a 重新赋值以后,再次更改 a 的值,b 的值并没有跟着变化。实际上,在执行 a = [4, 5, 6] 的时候,a 的指针变成了指向新的列表 [4, 5, 6],而 b 的指针并没有改变,依旧指向原来的列表 [1, 2, 3, 4]。
我们可以用示例图来看下这个过程。
- Python 解释器在执行
a = [1, 2, 3]时,变量a通过一个指针指向列表[1, 2, 3]。
- 在执行
b = a时,变量b也通过一个指针指向列表[1, 2, 3]。
- 在执行
a.append(4)时,实际上就是在更改变量a和变量b所指向的同一个列表对象。
- 将变量
a重新赋值,变量b依旧指向原来的列表对象。
- 此时再次对变量
a进行更改a.append(7),变量b的值不变。
对象的拷贝
由上面的赋值操作我们可以知道,在 Python 中如果将一个变量赋值给另一个变量,那么它们最终将指向同一个对象。然而有些时候我们需要创建一个与原对象有着相同值的新对象,但是不想让它们还是同一个对象,即如果改变其中一个对象,那么另一个对象不会跟着改变。这个时候,就需要用到对象的拷贝的知识了。
对于拷贝,Python 专门提供了一个 copy 模块。拷贝可以分为 浅拷贝 和 深拷贝。
- 先来看看浅拷贝
1 | import copy |
copy.copy 就是 Python 提供的浅拷贝方法。浅拷贝会在内存中重新创建一个新的对象,但对象中内部的元素还是对原对象内部元素的引用。
来看下面的例子:
1 | import copy |
上面的例子中,首先创建了一个列表 a,其内部还嵌套了另外两个列表,然后对 a 进行了 浅拷贝 操作,得到 b。
执行 a.append(11) 操作的时候,不会对 b 产生影响,这是 浅拷贝 的性质决定的,b 是一个新的对象。
执行 a[0].append(22) 的时候,b 的值会跟着改变,这就是上面所说的,浅拷贝时,新对象中内部的元素还是对原对象内部元素的引用。
由此可见,浅拷贝 只是对嵌套对象的顶层拷贝,并不会拷贝其内部元素。因此有些时候操作还是会产生一些副作用,要想完全复制一个对象,就需要用到 深拷贝。
- 深拷贝
Python 提供了 copy.deepcopy 可以对对象进行深度拷贝。深拷贝 同样会创建一个新的对象,并且还会将原对象内部的元素以递归的方式进行完全拷贝。这样就实现了完全复制一个对象的目的。
1 | import copy |
使用 深拷贝,当改变变量 a 的值的时候,就不会对变量 b 的值产生影响了。这就是所谓的 深拷贝。
上面已经对 深拷贝、浅拷贝 进行了大致的介绍,不过还有些细节部分需要我们注意。
1 | import copy |
出现以上现象其实是因为元组为不可变对象,因此无需重新分配内存并创建一个新的对象。所以,对于不可变对象来说,浅拷贝、深拷贝 最终的结果相同,都是指向原有的对象。但也有例外,就是当元组内部嵌套可变类型的时候。
1 | import copy |
上面示例的结果其实并没有脱离 浅拷贝 和 深拷贝 的特性,只不过是由于顶层对象是不可变类型,没有复制的必要,所以 浅拷贝 的对象和原对象还是同一个对象,原对象改变,浅拷贝 的对象也跟着改变。但是由于元组内部嵌套的对象是列表,列表又是可变类型,所以 深拷贝 就会递归的对其进行拷贝。
其实拷贝对于不可变对象来说,作用不大,真正有用的地方是在于可变对象的拷贝操作,并且只有被拷贝对象为嵌套对象的时候,才能够体现出差异。浅拷贝 只是对对象的顶层拷贝,深拷贝 是对对象的完全拷贝。
对于序列的 切片 操作,数据类型的构造器,实际上它们都相当于 浅拷贝。
1 | a = [1, 2, 3] |
对于自定义对象,还可以通过实现 __copy__、__deepcopy__ 两个方法来定义 浅拷贝 和 深拷贝 的结果。
1 | import copy |
1 | c |
你并不需要死记硬背来记下拷贝的特性,实际上,你只需要了解 Python 中可变类型和不可变类型的特点,就能够很容易理解了。在实际工作中,你可以根据自己的需要,使用 浅拷贝 或者 深拷贝 来解决遇到的问题。