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)) # print(id(b)) #
所以原理如下图所示:
因此,只要是在地址值: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)) #
print(id(b)) #
果然就不一样了。我们再通过一个图来看下深拷贝的原理:
三、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)) #
print(id(d)) # print(id(c[0])) #
print(id(d[0])) # print(id(a)) #
print(id(b)) #
根据代码,我们可以看到,当有嵌套对象,也就是文档中提到的递归对象,从结果我们可以看到,嵌套对象会进行递归的深拷贝。即如果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)) #
print(id(d)) # 4322576584 d和c地址不同,进一步说明外层是深拷贝 print(id(c[0])) #
print(id(d[0])) # 4322575176 c[0]和d[0]地址相同,进一步说明内层是浅拷贝 print(id(a)) #
print(id(b)) #
【注意】对于copy()方法,有特殊情况,比如元组类型,代码示例如下:
import copy
a = [1, 2, 3]
b = [3, 4, 5] c = (a, b) # 列表改成元组 d = copy.copy(c) print(id(c)) #
print(id(d)) # 4303015752 d和c地址相同 print(id(c[0])) #
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核心编程第二版笔记
python核心编程第二版笔记由网友提供:open168 python核心编程--笔记(很详细,建议收藏) 解释器options:1.1 –d 提供调试输出1.2 –O 生成优化的字节码(生成 ...
- 学习《Python核心编程》做一下知识点提要,方便复习(一)
学习<Python核心编程>做一下知识点提要,方便复习. 计算机语言的本质是什么? a-z.A-Z.符号.数字等等组合成符合语法的字符串.供编译器.解释器翻译. 字母组合后产生各种变化拿p ...
- python核心编程--笔记
python核心编程--笔记 的解释器options: 1.1 –d 提供调试输出 1.2 –O 生成优化的字节码(生成.pyo文件) 1.3 –S 不导入site模块以在启动时查找pyt ...
- Python核心编程第二版(中文).pdf 目录整理
python核心编程目录 Chapter1:欢迎来到python世界!-页码:7 1.1什么是python 1.2起源 :罗萨姆1989底创建python 1.3特点 1.3.1高级 1.3.2面向 ...
- Python中的浅拷贝与深拷贝
编者注:本文主要参考了<Python核心编程(第二版)> 以下都是参考资料后,我自己的理解,如有错误希望大家不吝赐教. 大家有没有遇到这样一种情况,对象赋值后,对其中一个变量进行修改,另外 ...
- python核心编程--笔记(不定时跟新)(转)
的解释器options: 1.1 –d 提供调试输出 1.2 –O 生成优化的字节码(生成.pyo文件) 1.3 –S 不导入site模块以在启动时查找python路径 1.4 –v ...
- python核心编程笔记(转)
解释器options: 1.1 –d 提供调试输出 1.2 –O 生成优化的字节码(生成.pyo文件) 1.3 –S 不导入site模块以在启动时查找python路径 1.4 –v 冗 ...
- Python核心编程(第二版)PDF
Python核心编程(第二版) 目录 第1部分 Python核心第1章 欢迎来到Python世界1.1 什么是Python1.2 起源1.3 特点1.3.1 高级1.3.2 面向对象1.3.3 可升级 ...
- python核心编程(第二版)习题
重新再看一遍python核心编程,把后面的习题都做一下.
随机推荐
- 如何搭建一个B2B电商的跨境系统网站?
国内的B2B跨境电商系统开发定制如何做才符合标准?商家怎么搭建专属的电商供应链系统?目前并不是大多数电商行业的公司可以应对得起组建团队来做,下面分享下大概的建设供应链商城网站思路和步骤(以数商云跨境电 ...
- 随机生成N个字符(包含数字和字母)
'************************************************************* ' Name: GetRandomString ' Purpose: 随机 ...
- 如何生成CA证书
一般情况下,如果能找到可用的证书,就可以直接使用,只不过会因证书的某些信息不正确或与部署证书的主机不匹配而导致浏览器提示证书无效,但这并不影响使用.需要手工生成证书的情况有:找不到可用的证书需要配置双 ...
- 制作多级菜单hide()与show() toggle()
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- C# 把Div变为滚动条
<div runat="server" style="overflow:auto;width:350px;height:200px" > <a ...
- for循环,for in和for of的区别
最近在写代码,总在被烦恼着到底遍历的时候到底该使用for循环,还是使用for in或者for of ,今天查了以下 ,写出来. 我们一般用for循环来遍历数组,因它可以按顺序的返回每一个索引的值或者 ...
- 自学Python全栈开发的第二次笔记(Python需要注意的地方)
好几天没写blog了,今天整理整理.写blog一定要坚持下去. Python解释器 #!/usr/bin/env python #-*-coding:utf-8-*- # 无效的内容,只 ...
- es6+require混合开发,兼容es6 module,import,export
近一年,一直很忙,做了不少的项目,不过都不是太满意,毕竟是别人的作品,不好意思写出来.最近打算开发一个es6的项目,项目中用到require,本文主要讲解es6的module规范怎么与require的 ...
- Python爬虫(十三)_案例:使用XPath的爬虫
本篇是使用XPath的案例,更多内容请参考:Python学习指南 案例:使用XPath的爬虫 现在我们用XPath来做一个简单的爬虫,我们尝试爬取某个贴吧里的所有帖子且将该帖子里每个楼层发布的图片下载 ...
- 酷狗歌曲缓存kgtemp转mp3工具
一直用网易音乐听歌,不过网易的歌曲版权确实是少了一些,在酷狗上可以找到,但收费歌曲只能试听不能下载. 寻找方案 从设置里可以看出,酷狗会设置缓存目录,试听的歌曲存放到这个缓存里. 打开缓存目录: 可以 ...