详解Python核心编程中的浅拷贝与深拷贝

 更新时间:2018年01月07日 09:18:49   投稿:laozhang   我要评论
本片文章给大家讲述了Python核心编程中的浅拷贝与深拷贝的相关知识点,有需要的朋友跟着学习下吧。

一、问题引出浅拷贝

首先看下面代码的执行情况:

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模块方法简介

从深拷贝的实现过程,我们知道copy模块,也使用了里面的deepcopy()方法。下面我们来介绍下copy模块中的copy()与deepcopy()方法。

首先介绍我们已经使用过的deepcopy()方法,官方文档介绍如下:

简单解释下文档中对这个方法的说明:

1. 返回值是对这个对象的深拷贝

2. 如果拷贝发生错误,会报copy.err异常

3. 存在两个问题,第一是如果出递归对象,会递归的进行拷贝,第二正因为会递归拷贝,会导致出现拷贝过多的情况

4. 关于两种拷贝方式的区别都是相对是引用对象

前两点很好理解,针对第三点,我们用代码进行解释:

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方法内部有判断,如果最外层的拷贝类型是不可变类型,则进行浅拷贝,反之则进行深拷贝。

至此,我们应该对浅拷贝的概念进行进一步加深理解:

如果对象中的所有元素,有一个是引用拷贝,则定义为是浅拷贝。(该定义不是官方定义,只是个人理解)

四、关于“修改”的一点说明

前面提到了修改变量,我认为修改是有两种方式,第一种在原对象上进行修改,第二种就是重新赋值。看如下代码:

import copy
a = [1, 2, 3]
b = a
a = [3, 4, 5]
print(a) # [3, 4, 5]
print(b) # [1, 2, 3]

同样是浅拷贝,但是发现修改a之后,b没有发生变化。

在修改的时候,我们很容易想当然的通过重新赋值的方式来修改,但其实这种修改方式是有问题的。当给a再次赋值的时候,其实是将a重新指向了另外一块地址区域,而原来的[1, 2, 3]那块地址区域是没有发生任何变化的,所以对于b来说,它指向的东西并没有改变。

这也解释了之前文档中关于deepcopy方法的一个说明,为什么只对引用对象有用,因为简单类型修改的方式就是重新赋值。简单理解就是你没办法通过简单类型的变量直接通过.来调用自身的方法,都只能重新赋值来改变,那么都会指向新的地址。

以上就是本片文章关于Python核心编程中的浅拷贝与深拷贝的全部内容,大家可以把学习的心得写在下面的留言区,感谢你对澳门金沙网上娱乐的支持。

相关文章

  • Python机器学习k-近邻算法(K Nearest Neighbor)实例详解

    Python机器学习k-近邻算法(K Nearest Neighbor)实例详解

    这篇文章主要介绍了Python机器学习k-近邻算法(K Nearest Neighbor),结合实例形式分析了k-近邻算法的原理、操作步骤、相关实现与使用技巧,需要的朋友可以参考下
    2018-06-06
  • Python元字符的用法实例解析

    Python元字符的用法实例解析

    这篇文章主要介绍了Python元字符的用法实例解析,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • Python heapq使用详解及实例代码

    Python heapq使用详解及实例代码

    这篇文章主要介绍了Python heapq使用详解及实例代码的相关资料,需要的朋友可以参考下
    2017-01-01
  • Python3内置模块random随机方法小结

    Python3内置模块random随机方法小结

    这篇文章主要介绍了Python3内置模块random随机方法小结,random是Python中与随机数相关的模块,其本质就是一个伪随机数生成器,我们可以利用random模块基础生成各种不同的随机数,以及一些基于随机数的操作,需要的朋友可以参考下
    2019-07-07
  • python使用rpc框架gRPC的方法

    python使用rpc框架gRPC的方法

    这篇文章主要为大家详细介绍了python使用rpc框架gRPC的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-08-08
  • Django模板导入母版继承和自定义返回Html片段过程解析

    Django模板导入母版继承和自定义返回Html片段过程解析

    这篇文章主要介绍了Django模板导入母版继承和自定义返回Html片段过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • 基于MATLAB和Python实现MFCC特征参数提取

    基于MATLAB和Python实现MFCC特征参数提取

    这篇文章主要介绍了基于MATLAB和Python实现MFCC特征参数提取,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • python win32 简单操作方法

    python win32 简单操作方法

    下面小编就为大家带来一篇python win32 简单操作方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • 浅谈python在提示符下使用open打开文件失败的原因及解决方法

    浅谈python在提示符下使用open打开文件失败的原因及解决方法

    今天小编就为大家分享一篇浅谈python在提示符下使用open打开文件失败的原因及解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-11-11
  • python字符串常用方法

    python字符串常用方法

    这篇文章主要介绍了python字符串常用方法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-06-06

最新评论