0. 介绍
浅拷贝:只拷贝父对象,不拷贝内部子对象
nums = [1, 2, 3, [4, 5]]
nums_copy = nums.copy()
nums[0], nums[3][0] = 8, 100
print(nums)
print(nums_copy)
# 输出
# nums: [8, 2, 3, [100, 5]]
# nums_copy: [1, 2, 3, [100, 5]]
深拷贝:完全拷贝了父对象及其子对象
import copy
nums = [1, 2, 3, [4, 5]]
nums_copy = copy.deepcopy(nums)
nums[0], nums[3][0] = 8, 100
print(nums)
print(nums_copy)
# 输出
# nums: [8, 2, 3, [100, 5]]
# nums_copy: [1, 2, 3, [4, 5]]
深拷贝与浅拷贝的使用大致如此,只是作为一个API记住没有任何的难度,如果你的目标仅仅在于了解如何使用,那么本文对你的帮助到此就为止了。如果你有足够的好奇心,想了解其中的原理,那么可以继续往下看,我会对它的原理进行详细分析。
1. 对象模型
为了让大家更好以及更透彻的理解python的浅拷贝与深拷贝,我们先来简单的说一下python的对象模型。如果你有研究过python的源码,那么对于对象模型就一定不会感到陌生。
首先这里要给出一个重要的结论:在python中,一切皆对象
和C++以及Java一样,python也是一个面向对象的程序语言,但不同的是,python是一个 完全面向对象 的语言,怎么去理解这句话呢?请看下面这个例子
以C语言为例,定义一个变量 a 并初始化为1,再定义一个变量 b,以 a 赋值:
int a = 1;
int b = a;
C语言中,这两个变量在内存的分配情况就如上图所示,二者位于不同的内存区域。这也是比较符合我们认知的,那么在python中也是如此吗?我们来看看下面一段代码:
惊奇地看到, a 和 b 这两个变量的地址居然是相同的!这似乎不符合常理!但这也恰恰印证了我们前面提到了结论,在python中,一切皆对象, a 和 b 仅仅只是两个名字,它们都指向了内存中同一个整数对象:
如果这还不能打动到你,我们再看一段代码:
这里只对变量 a 做了个自增运算,但同一个变量自增前与自增后的地址竟然发生了变化!这是怎么回事?
其实原因也很简单,整数对象在python中被称为 不可变对象,也就是说它们的值一旦确定就不可更改,我们这里对变量 a 做的自增运算,会让python在内存中重新创建一个整数对象,两个不同的对象在内存中的地址自然也是不同的:
既然有 不可变对象,那么对应的一定存在 可变对象,在python中典型的可变对象就是列表(List):
往列表里头追加数据,发现列表对象还是原来那个,只不过多了一个元素。实际上,列表对象内部维护了一个 动态数组 ,存储元素对象的指针。列表对象增减元素,需要修改该数组,例如,追加元素 3 :
2. 直接赋值
了解python对象模型的基本概念之后,从这个角度来理解浅拷贝与深拷贝就会轻松不少,在此之前,我们先来说一下直接赋值,以python基础数据结构列表(List)为例:
这里我们只改变了 nums 列表中的元素,但是却影响到了 nums_copy,通过前面学到的知识,很容易明白变量只是个名字,二者其实指向的是同一个对象,即同一个内存地址(不信的话可以亲自动手打印一下这两个列表的地址,会发现地址值是相同的),所以只要修改其中一个,另一个势必也会跟着改变:
3. 浅拷贝
同样对于列表 nums 和 nums_copy,如果采用浅拷贝的方式(这里的nums[:]
等同于nums.copy()
),二者就不会指向同一个地址空间了,即它们是两个独立的列表对象,但是它们的子对象仍然是相同的,文字叙述起来可能还是比较晦涩,我们直接看代码和图:
nums = [1, 2, 3, [4, 5]]
nums_copy = nums[:]
print(id(nums))
print(id(nums_copy))
# 输出
# 2695619174984
# 2695620417672
通过地址打印,首先我们可以确定的是,浅拷贝之后,列表变量 nums 与 nums_copy 的指向已经不是同一个对象了,因为它们的地址不同
nums = [1, 2, 3, [4, 5]]
nums_copy = nums[:]
print(id(nums[0]))
print(id(nums_copy[0]))
print(id(nums[3]))
print(id(nums_copy[3]))
# 输出
# 140719115661056
# 140719115661056
# 2689543594504
# 2689543594504
接着我们打印了两个列表中的部分相同位置的元素,发现地址还是一样,这说明了,二者虽然已经是两个不同的列表对象,但它们的子对象仍然是相同的,现在我们尝试着修改其中一个列表的值,看看会有什么样有意思的事情发生:
nums = [1, 2, 3, [4, 5]]
nums_copy = nums[:]
nums[0] = 8
nums[3][0] = 100
print(nums)
print(nums_copy)
# 输出
# nums: [8, 2, 3, [100, 5]]
# nums_copy: [1, 2, 3, [100, 5]]
从打印信息看来,这里的子对象除了整数对象之外还有一个子列表对象,那么为什么整数对象的值没有更新,但子列表对象的值更新了呢?其实结合我们前面说到的 可变对象 与 不可变对象 就很好理解:
4. 深拷贝
当您阅读到这里的时候,相信您已经对python的对象模型有了更加深刻的理解,深拷贝相较于浅拷贝唯一的区别就是我在介绍中说的那句话:深拷贝完全拷贝了父对象及其子对象
我们最后再走一遍流程,首先通过代码来看看深拷贝之后 nums 以及 nums_copy 的地址信息:
可以明显的看到经过深拷贝之后,不仅父对象的地址发生了变化,连子对象的地址也发生了变化,这表明了二者已经没有了任何交集,是两个彻底不同的对象。在这种情况下,我们再去修改 nums 的值,不会对 nums_copy 产生任何的影响:
那么它们在内存中的分配示意图应该是这样的: