变量的赋值
在 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 | 1, 2, 3] a = [ |
在 Python 中可以通过 is
来比较两个变量是否为同一个对象,所以 a
和 b
本质上是同一个对象,只不过是这个对象的两个 名字
罢了。
此时,将列表 [4, 5, 6]
赋值给变量 a
:a = [4, 5, 6]
,再次对变量 a
进行修改,会得到什么样的结果呢?
1 | 4, 5, 6] a = [ |
可以看到,将变量 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 | 1, 2, 3] a = [ |
对于自定义对象,还可以通过实现 __copy__
、__deepcopy__
两个方法来定义 浅拷贝
和 深拷贝
的结果。
1 | import copy |
1 | c |
你并不需要死记硬背来记下拷贝的特性,实际上,你只需要了解 Python 中可变类型和不可变类型的特点,就能够很容易理解了。在实际工作中,你可以根据自己的需要,使用 浅拷贝
或者 深拷贝
来解决遇到的问题。