Python面试题目之深浅拷贝浅析
# copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。 **只是简单的指针赋值 # copy.deepcopy 深拷贝 拷贝对象及其子对象 **指针赋值,且内容拷贝
用一个简单的例子说明如下:
>>>import copy
>>>a = [1, 2, 3, 4, ['a', 'b', 'c']]
>>> b = a
>>> c = copy.copy(a)
>>> d = copy.deepcopy(a)
很容易理解:
a是一个列表,表内元素a[4]也是一个列表(也就是一个内部子对象);
b是对a列表的又一个引用,所以a、b是完全相同的,可以通过id(a)==id(b)证明。
第4行copy.copy()是浅拷贝;
第5行copy.deepcopy()是深拷贝,通过id(c)和id(d)可以发现他们不相同,且与id(a)都不相同;
>>> id(a)
19276104
>>> id(b)
19276104
>>> id(c)
19113304
>>> id(d)
19286976
至于如何看深/浅拷贝的区别,可以通过下面的操作来展现:
>>> a.append(5) #操作1
>>> a[4].append('hello') #操作2
这时再查看结果:
>>> a
[1, 2, 0, 4, ['a', 'b', 'c', 'hello'], 5]
>>> b
[1, 2, 0, 4, ['a', 'b', 'c', 'hello'], 5]
>>> c
[1, 2, 3, 4, ['a', 'b', 'c', 'hello']]
>>> d
[1, 2, 3, 4, ['a', 'b', 'c']]
可以发现:
a、b受了操作1、2的影响;
c只受操作2影响;
d不受影响;
a、b结果相同很好理解。
由于c是a的浅拷贝,只拷贝了父对象,因此a的子对象( ['a', 'b', 'c', 'hello'])改变时会影响到c;
d是深拷贝,完全不受a的影响;
浅拷贝是指拷贝的只是原对象元素的引用,换句话说,浅拷贝产生的对象本身是新的,但是它的内容不是新的,只是对原对象的一个引用。这里有个例子
>>> L1=[[1, 2], 3, 4]
>>> L2 = L1[:] #利用切片完成一次浅拷贝
>>> id(L1)
3084416588L
>>> id(L2)
3084418156L
>>> L1[0][0] = 5
>>> print(L1)
[[5, 2], 3, 4]
>>> print(L2)
[[5, 2], 3, 4]
可以看到,浅拷贝生产了一个新的对象列表L2,但是列表L2的内容确实对L1的引用,所以但改变L1中值的时候,L2的值也跟着变化了。
但是有点需要特别提醒的,如果对象本身是不可变的,那么浅拷贝时也会产生两个值,见这个例子:
>>> L1 = [1, 2]
>>> L2 = L1[:]
>>>print((L2)
[1, 2]
>>> print(L1)
[1, 2]
>>> L1[1]=111
>>> print(L1)
[1, 111]
>>> print(L2)
[1, 2]
为什么L2的第二个元素没有变成111呢?因为数字在python中是不可变类型!!
这个顺便回顾下Python标准类型的分类:
# 可变类型: 列表,字典
# 不可变类型:数字,字符串,元组
理解了浅拷贝,深拷贝是什么自然就很清楚了。
python中有一个模块copy,deepcopy函数用于深拷贝,copy函数用于浅拷贝。
最后,对象的赋值是深拷贝还是浅拷贝?
对象赋值实际上是简单的对象引用
>>> a = 1
>>> id(a)
135720760
>>> b = a
>>> id(b)
135720760
a和b完全是一回事。
详细请看下文:
Python中 copy, deepcopy 的区别及原因
虽然第一次听说 Python 中的 copy 与 deep copy 是作为 copy
模块中的两个 method。但它们其实是 OOP 语言中常见的概念。这里只说 Python,其他语言不了解。
Python 的 copy 模块中的
copy()
method 其实是与 deep copy 相对的 shallow copy。copy.copy(object)
就等于是对 object 做了 shallow copy。
先说结论:
- 对于简单的 object,用 shallow copy 和 deep copy 没区别:
>>> import copy
>>> origin = 1
>>> cop1 = copy.copy(origin)
#cop1 是 origin 的shallow copy
>>> cop2 = copy.deepcopy(origin)
#cop2 是 origin 的 deep copy
>>> origin = 2
>>> origin
2
>>> cop1
1
>>> cop2
1
#cop1 和 cop2 都不会随着 origin 改变自己的值
>>> cop1 == cop2
True
>>> cop1 is cop2
True
- 复杂的 object, 如 list 中套着 list 的情况,shallow copy 中的 子list,并未从原 object 真的「独立」出来。
也就是说,如果你改变原 object 的子 list 中的一个元素,你的 copy 就会跟着一起变。这跟我们直觉上对「复制」的理解不同。
看代码更容易理解些:
>>> import copy
>>> origin = [1, 2, [3, 4]]
#origin 里边有三个元素:1, 2,[3, 4]
>>> cop1 = copy.copy(origin)
>>> cop2 = copy.deepcopy(origin)
>>> cop1 == cop2
True
>>> cop1 is cop2
False
#cop1 和 cop2 看上去相同,但已不再是同一个object
>>> origin[2][0] = "hey!"
>>> origin
[1, 2, ['hey!', 4]]
>>> cop1
[1, 2, ['hey!', 4]]
>>> cop2
[1, 2, [3, 4]]
#把origin内的子list [3, 4] 改掉了一个元素,观察 cop1 和 cop2
可以看到 cop1
,也就是 shallow copy 跟着 origin 改变了。而 cop2
,也就是 deep copy 并没有变。
似乎 deep copy 更加符合我们对「复制」的直觉定义: 一旦复制出来了,就应该是独立的了。如果我们想要的是一个字面意义的「copy」,那就直接用 deep_copy
即可。
那么为什么会有 shallow copy 这样的「假」 copy 存在呢? 这就是有意思的地方了。
Python 与众不同的变量储存方法
Python 存储变量的方法跟其他 OOP 语言不同。它与其说是把值赋给变量,不如说是给变量建立了一个到具体值的 reference。
当在 Python 中 a = something
应该理解为给 something 贴上了一个标签 a。当再赋值给 a
的时候,就好象把 a 这个标签从原来的 something 上拿下来,贴到其他对象上,建立新的 reference。 这就解释了一些 Python 中可能遇到的诡异情况:
>>> a = [1, 2, 3]
>>> b = a
>>> a = [4, 5, 6] //赋新的值给 a
>>> a
[4, 5, 6]
>>> b
[1, 2, 3]
# a 的值改变后,b 并没有随着 a 变 >>> a = [1, 2, 3]
>>> b = a
>>> a[0], a[1], a[2] = 4, 5, 6 //改变原来 list 中的元素
>>> a
[4, 5, 6]
>>> b
[4, 5, 6]
# a 的值改变后,b 随着 a 变了
上面两段代码中,a
的值都发生了变化。区别在于,第一段代码中是直接赋给了 a
新的值(从 [1, 2, 3]
变为 [4, 5, 6]
);而第二段则是把 list 中每个元素分别改变。
而对 b
的影响则是不同的,一个没有让 b
的值发生改变,另一个变了。怎么用上边的道理来解释这个诡异的不同呢?
首次把 [1, 2, 3]
看成一个物品。a = [1, 2, 3]
就相当于给这个物品上贴上 a
这个标签。而 b = a
就是给这个物品又贴上了一个 b
的标签。
第一种情况:
a = [4, 5, 6]
就相当于把 a
标签从 [1 ,2, 3]
上撕下来,贴到了 [4, 5, 6]
上。
在这个过程中,[1, 2, 3]
这个物品并没有消失。 b
自始至终都好好的贴在 [1, 2, 3]
上,既然这个 reference 也没有改变过。 b
的值自然不变。
第二种情况:
a[0], a[1], a[2] = 4, 5, 6
则是直接改变了 [1, 2, 3]
这个物品本身。把它内部的每一部分都重新改装了一下。内部改装完毕后,[1, 2, 3]
本身变成了 [4, 5, 6]
。
而在此过程当中,a
和 b
都没有动,他们还贴在那个物品上。因此自然 a
b
的值都变成了 [4, 5, 6]
。
这部分搞明白了之后再去看 copy 的区别就容易多了。
言归正传,Copy时候到底发生了什么
最初对 copy 产生疑惑,是有一次想对一个复杂的 list 遍历并且做修改。
这种情况下,最好先建立一个 copy 出来:
If you need to modify the sequence you are iterating over while inside the loop (for example to duplicate selected items), it is recommended that you first make a copy. Iterating over a sequence does not implicitly make a copy.
于是想当然用了 copy.copy()
。结果却发现本体与 copy 之间并不是独立的。有的时候改变其中一个,另一个也会跟着改变。也就是本文一开头结论中提到的情况:
>>> import copy
>>> origin = [1, 2, [3, 4]]
#origin 里边有三个元素:1, 2,[3, 4]
>>> cop1 = copy.copy(origin)
>>> cop2 = copy.deepcopy(origin)
>>> cop1 == cop2
True
>>> cop1 is cop2
False
#cop1 和 cop2 看上去相同,但已不再是同一个object
>>> origin[2][0] = "hey!"
>>> origin
[1, 2, ['hey!', 4]]
>>> cop1
[1, 2, ['hey!', 4]]
>>> cop2
[1, 2, [3, 4]]
#把origin内的子list [3, 4] 改掉了一个元素,观察 cop1 和 cop2
官方解释是这样的:
The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances):
A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.
两种 copy 只在面对复杂对象时有区别,所谓复杂对象,是指对象中含其他对象(如复杂的 list 和 class)。
由 shallow copy 建立的新复杂对象中,每个子对象,都只是指向自己在原来本体中对应的子对象。而 deep copy 建立的复杂对象中,存储的则是本体中子对象的 copy,并且会层层如此 copy 到底。
这个解释看上去略抽象。
先看这里的 shallow copy。 如图所示,cop1 就是给当时的 origin 建立了一个镜像。origin 当中的元素指向哪, cop1 中的元素就也指向哪。这就是官方 doc 中所说的 inserts references into it to the objects found in the original
。
这里的关键在于,origin[2]
,也就是 [3, 4] 这个 list。根据 shallow copy 的定义,在 cop1[2]
指向的是同一个 list [3, 4]。那么,如果这里我们改变了这个 list,就会导致 origin 和 cop1 同时改变。这就是为什么上边 origin[2][0] = "hey!"
之后,cop1 也随之变成了 [1, 2, ['hey!', 4]]
。
再来看 deep copy。 从图中可以看出,cop2 是把 origin 每层都 copy 了一份存储起来。这时候的 origin[2]
和 cop2[2]
虽然值都等于 [3, 4],但已经不是同一个 list了。
既然完全独立,那无论如何改变其中一个,另一个自然不会随之改变。
Python面试题目之深浅拷贝浅析的更多相关文章
- python学习笔记:深浅拷贝的使用和原理
在理解深浅拷贝之前,我们先熟悉下变量对象和数据类型 1.变量和对象 变量-引用-对象(可变对象,不可变对象) 在python中一切都是对象,比如[1,2],'hello world',123,{'k1 ...
- python之路(三)-深浅拷贝
深浅拷贝用法来自copy模块. 导入模块:import copy 浅拷贝:copy.copy 深拷贝:deepcopy 字面理解:浅拷贝指仅仅拷贝数据集合的第一层数据,深拷贝指拷贝数据集合的所有层.所 ...
- python变量存储和深浅拷贝
python的变量及其存储 在高级语言中,变量是对内存及其地址的抽象.对于python而言,python的一切变量都是对象,变量的存储,采用了引用语义的方式,存储的只是一个变量的值所在的内存地址,而不 ...
- python内存相关以及深浅拷贝讲解
3.9 内存相关 3.9.1 id,查看内存地址 >>> v1 = [11,22,33] >>> v2 = [11,22,33] >>> prin ...
- Python collection模块与深浅拷贝
collection模块是对Python的通用内置容器:字典.列表.元组和集合的扩展,它包含一些专业的容器数据类型: Counter(计数器):dict子类,用于计算可哈希性对象的个数. Ordere ...
- Python基础入门知识点——深浅拷贝
深浅拷贝 对象引用.浅拷贝.深拷贝(拓展.难点.重点) Python中,对象的赋值,拷贝(深/浅拷贝)之间是有差异的,如果使用的时候不注意,就可能产生意外的结果 其实这个是由于共享内存导致的结果 拷贝 ...
- 从零开始的Python学习Episode 8——深浅拷贝
深浅拷贝 一.浅拷贝 列表中存储的是数据的内存地址,当我们要查询或修改列表中的数据时,我们是通过列表中的地址找到要访问的内存.当我们修改列表中的数据时,如果修改的是一个不可变类型(整型,长整型,浮点数 ...
- python set集合 以及 深浅拷贝
set集合 特点: 无序, 不重复, 元素必须可哈希(不可变) 作用: 去重复 本身是可变的数据类型. 有增删改查操作. frozenset()冻结的集合. 不可变的. 可hash的 深浅拷贝() 1 ...
- python神坑系列之深浅拷贝
深浅拷贝 1.注意在拷贝中只有使用了.deepcopy方法才能进行深度拷贝!其余的一律是浅拷贝 #深拷贝import copy lst = copy.deepcopy(lst1) 浅拷贝: 拷贝的是 ...
随机推荐
- 关于js中定时器的返回值问题
在js中,我们常常会用到定时器来处理各种各样的问题,当我们需要清除定时器的时候,我们常常会定义一个值来接受定时器的返回值,然后再把定义好的这个值写到清除定时器的括弧后面,如: var times = ...
- 5个基于Web的建模工具
本文介绍 5 款很棒的直接可以在浏览器使用的建模工具,无需单独安装软件. 1. Creately提供在线图表和协助功能,包含多种建模语言(UML)支持,这里有一个简单的演示:here 2.Diagra ...
- 【黑金ZYNQ7000系列原创视频教程】02.视频接口——hdmi编码输出实验
黑金论坛地址: http://www.heijin.org/forum.php?mod=viewthread&tid=36636&extra=page%3D1 爱奇艺地址: http: ...
- linux awk时间计算脚本
在linux如果计划时间是个麻烦事, 用awk脚本如下 BEGIN {FS=":";OFS=":"} {total_seconds=total_seconds+ ...
- angular -- get请求该如何使用?
在做 angualr 的开发过程中,经常会用到的就是 ajax 请求.下面是 get 请求示例: 如果存在多个 get 请求可以考虑进行封装下: // get 携参数访问 ajaxGet(getUrl ...
- 【转】stm32中断嵌套全攻略
断断续续学习STM32一学期了,时间过的好快,现在对STM32F103系列单片机的中断嵌套及外部中断做一个总结,全当学习笔记.废话不多说,ARM公司的Cortex-m3 内核,支持256个中断,其中包 ...
- Windows系统下做定时任务为Oracle数据库每天自动备份
1.创建备份目录d:\backup, 创建批处理命令Bak.bat,编写备份脚本 ? 1 2 exp user/passwd@orcl DIRECT=Y BUFFER=100000 FILE=D:\b ...
- [python] python单元测试经验总结
python写单元大多数都会用到unittest和mock,测试代码覆盖率都会用到coverage,最后再用nose把所有的东西都串起来,这样每次出版本,都能把整个项目的单元测试都运行一遍. Unit ...
- postgresql架构基础(转)-(1)
PostgreSQL使用一种客户端/服务器的模型.一次PostgreSQL会话由下列相关的进程(程序)组成: 一个服务器进程,它管理数据库文件.接受来自客户端应用与数据库的联接并且代表客户端在数据库上 ...
- Jenkins的参数化构建
一.参数化构建日志 1.查看效果 有时候开发需要查看服务器日志,传统的是需要运维登录服务器拉取开发所需要的服务日志,这么做的弊端是:1.如果日志比较大,拉取耗费时间.占用服务器资源.2.占用运维不必要 ...