首先看下面代码的执行情况:
- a = [1, 2, 3]
- print('a = %s' % a) # a = [1, 2, 3]
- b = a
- print('b = %s' % b) # b = [1, 2, 3]
- a.append(4) # 对a进行修改
- print('a = %s' % a) # a = [1, 2, 3, 4]
- print('b = %s' % b) # b = [1, 2, 3, 4]
- b.append(5) # 对b进行修改
- print('a = %s' % a) # a = [1, 2, 3, 4, 5]
- print('b = %s' % b) # b = [1, 2, 3, 4, 5]
上面的代码比较简单,定义了一个变量 a,它是一个数值 [1, 2, 3] 的列表,通过一个简单的赋值语句 b = a 定义变量 b,它同样也是数值 [1, 2, 3] 的列表。
问题是:如果此时修改变量 a,对 b 会有影响吗?同样如果修改变量 b,对 a 又会有影响吗?
从代码运行结果可以看出,无论是修改 b 还是修改 a(注意这种修改的方式,是用 append,直接修改原列表,而不是重新赋值),都另一方都是有影响的。
当然这个原因其实很好理解,变量 a 指向的是列表 [1, 2, 3] 的地址值,当用 = 进行赋值运算时,b 的值也相应的指向的列表 [1, 2, 3] 的地址值。在 python 中,可以通过 id(变量)的方法来查看地址值,我们来查看下 a,b 变量的地址值,看是不是相等:
- # 注意,不同机器上,这个值不同,但只要a,b两个变量的地址值是一样的就能说明问题了
- print(id(a)) # 4439402312
- print(id(b)) # 4439402312
所以原理如下图所示:
因此,只要是在地址值:4439402312 上的列表进行修改的话,a,b 都会发生变化。(注意我这里说的修改,是在地址值为:4439402312 上的列表进行的修改,而不说对变量 a 进行修改,因为对变量 a 的修改方式有两种,本文结尾会解释为什么不说对变量 a 进行修改) 。所以我们便引出了以下概念:
对于这种是将引用进行拷贝赋值给另一个变量的方式(即拷贝的是地址值),我们称之为浅拷贝。
python 中实现深拷贝的方式很简单,只需要引入 copy 模块,调用里面的 deepcopy() 的方法即可,示例代码如下:
- import copy
- a = [1, 2, 3]
- b = copy.deepcopy(a)
- print('a = %s' % a) # a = [1, 2, 3]
- print('b = %s' % b) # b = [1, 2, 3]
- b.append(4)
- print('a = %s' % a) # a = [1, 2, 3]
- print('b = %s' % b) # b = [1, 2, 3, 4]
从代码执行情况来看,我们已经实现了深拷贝。这时我们再来看下两个变量的地址值:
- print(id(a)) # 4321416008
- print(id(b)) # 4321416200
果然就不一样了。我们再通过一个图来看下深拷贝的原理:
从深拷贝的实现过程,我们知道 copy 模块,也使用了里面的 deepcopy() 方法。下面我们来介绍下 copy 模块中的 copy() 与 deepcopy() 方法。
首先介绍我们已经使用过的 deepcopy() 方法,官方文档介绍如下:
简单解释下文档中对这个方法的说明:
1. 返回值是对这个对象的深拷贝
2. 如果拷贝发生错误,会报 copy.err 异常
3. 存在两个问题,第一是如果出递归对象,会递归的进行拷贝,第二正因为会递归拷贝,会导致出现拷贝过多的情况
前两点很好理解,针对第三点,我们用代码进行解释:
- import copy
- a = [1, 2, 3]
- b = [3, 4, 5]
- c = [a, b] # 列表嵌套
- d = copy.deepcopy(c)
- print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5]]
- print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
- c.append(4)
- print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5], 4]
- print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
- c[0].append(4) # 相当于a.append(4)
- print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
- print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
- # a.append(4)
- # print('c = %s' % c) # a = [1, 2, 3]
- # print('d = %s' % d) # b = [1, 2, 3]
- print(id(c)) # 4314188040
- print(id(d)) # 4314187976
- print(id(c[0])) # 4314186568
- print(id(d[0])) # 4314187912
- print(id(a)) # 4314186568
- print(id(b)) # 4314186760
根据代码,我们可以看到,当有嵌套对象,也就是文档中提到的递归对象,从结果我们可以看到,嵌套对象会进行递归的深拷贝。即如果 c 里有一个 a,那么不仅 c 会深拷贝,a 同样也会被深拷贝。原理如下图所求:
接下来我们再来看 copy() 方法:
官方文档解释的很简单,它返回的就是对象的浅拷贝。但其实它会对最外层进行深拷贝,而如果有多层,第二层以后进行的就是浅拷贝了。代码示例如下:
- import copy
- a = [1, 2, 3]
- b = [3, 4, 5]
- c = [a, b] # 列表嵌套
- d = copy.copy(c)
- print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5]]
- print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
- c.append(4)
- print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5], 4]
- print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]] 没有发生变化,说明外层是深拷贝
- c[0].append(4) # 相当于a.append(4)
- print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
- print('d = %s' % d) # d = [[1, 2, 3, 4], [3, 4, 5]] 发生了变化,说明内层是浅拷贝
- # a.append(4)
- # print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
- # print('d = %s' % d) # d = [[1, 2, 3, 4], [3, 4, 5]] 发生了变化,说明内层是浅拷贝
- print(id(c)) # 4322576648
- print(id(d)) # 4322576584 d和c地址不同,进一步说明外层是深拷贝
- print(id(c[0])) # 4322575176
- print(id(d[0])) # 4322575176 c[0]和d[0]地址相同,进一步说明内层是浅拷贝
- print(id(a)) # 4322575176
- print(id(b)) # 4322575368
【注意】对于 copy() 方法,有特殊情况,比如元组类型,代码示例如下:
- import copy
- a = [1, 2, 3]
- b = [3, 4, 5]
- c = (a, b) # 列表改成元组
- d = copy.copy(c)
- print(id(c)) # 4303015752
- print(id(d)) # 4303015752 d和c地址相同
- print(id(c[0])) # 4322575176
- print(id(d[0])) # 4322575176 c[0]和d[0]地址相同,进一步说明内层是浅拷贝
可以看到,这里哪怕是最外层,也是浅拷贝。
这里因为 copy 方法内部有判断,如果最外层的拷贝类型是不可变类型,则进行浅拷贝,反之则进行深拷贝。
来源: https://www.cnblogs.com/yrrAwx/p/8202838.html