Python的赋值,浅拷贝与深拷贝

时间:2022-05-21 21:17:24
一、基础知识

Python中有3个重要的概念:变量对象引用

在Python中,类型属于对象,而变量是无类型的。

举例来说,在Python中,给一个变量赋值,

a = 1

在Java中,给一个变量赋值是这样的:

int a = 1;

在这里, a 是一个变量,在Python中,并没有像在Java中显式地说明 a 的数据的类型 int. Python的编译器会自动判断 1 是一个 int 类型的对象。

而类型属于对象,应该是这样说,

在上面的Python代码中, a = 1 中的 数字 1 是一个 int 对象实例。对应的,还有 float, list,dict,tuple等类型的对象。这些常用的数据类型都是对象。

所以,a = 1的完整理解是:

1 是一个int 型的对象实例,a 是一个变量,一个指向一个int对象实例的对象的引用。

Python的赋值,浅拷贝与深拷贝

Python关于引用的内容:

为了简化内存管理,Python通过引用计数机制实现自动垃圾回收功能,Python中的每个对象都有一个引用计数,用来计数该对象在不同场所分别被引用了多少次。每当引用一次Python对象,相应的引用计数就增1,每当消毁一次Python对象,则相应的引用就减1,只有当引用计数为零时,才真正从内存中删除Python对象。

还有一点需要说明的是:

Python中对象有“可更改”(mutable)与“不可更改”(immutable)之分。strings, tuples, 和numbers是不可更改的对象,而list,dict等则是可以修改的对象。

有了上面的理解,对于Python的赋值,浅拷贝,深拷贝就容易懂了。

二、python的赋值:

引用计数的增加或减少。

a=1  # a 是一个指向 1 这个int对象实例的引用
id(a) # a 保存的内存地址
#
33782152
b=a # a 赋值给 b
id(b) # b 保存的内存地址与 a 保存的内存地址一致
#
33782152
c=b # b 赋值给 c
id(c) # c 保存的内存地址与 a 和 b 保存的内存地址一致
#
33782152
a=2 # 2 赋值给 a
id(a) # a 变成一个指向 2 这个int对象实例的引用,保存的地址也发生变化
#
33782140

三、python的浅拷贝

浅拷贝主要有三种方法:

1. 使用切片[:]操作进行拷贝

2. 使用工厂函数(如list/dir/set)等进行拷贝

3. copy.copy()

对不可更改的对象来说,浅拷贝等同于赋值,也是对象引用的计数(这句话可能不准确,对不可更改的对象来说,没有拷贝这一说)。

而对于可更改的对象,浅拷贝创建了一个新的对象,也会有一个新的对象引用指向该对象,但是对象的内容中的可变对象依然是引用。

下图可以说明:

Python的赋值,浅拷贝与深拷贝

list = [[1,2,3],'aaa'] # list是一个既包含可变对象inlist和不可变对象str的list
list
[[
1, 2, 3], 'aaa']
import copy # 导入copy 模块
copylist = copy.copy(list)
copylist
[[
1, 2, 3], 'aaa'] # copylist 是 list的一个浅拷贝对象
copylist[1] = 'bbb'
copylist
[[
1, 2, 3], 'bbb'] # copylist的不可变对象元素被更改
list
[[
1, 2, 3], 'aaa'] # list的不可变对象并未发生变化
copylist[0][2] = 6 # copylist的可变对象list的元素被重新赋值
copylist
[[
1, 2, 6], 'bbb'] # copylist的可变对象list的元素发生变化
list
[[
1, 2, 3], 'aaa'] # 与此同时, list的可变对象inlist的元素也发生同样的变化,因为list和copylist的inlist元素是指向同一个list对象的引用

 

四、python的深拷贝

既复制一个容器对象,也复制了它里面的所有元素(包含元素的子元素)。

使用copy.deepcopy()方法。空间和时间的开销也比较大。

也就是按照下图的红线将list和copylist的可变元素的引用对象割裂成两个完全没有关系的对象。

Python的赋值,浅拷贝与深拷贝

 

 

参考:

深入Python(4):深拷贝和浅拷贝

Python:传值 and 传引用(转)