其实呢,浅拷贝copy()与深拷贝deepcopy()之间的区分必须要涉及到python对于数据的存储方式。

首先直接上结论:

—–我们寻常意义的复制就是深复制,即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。 
—–而浅复制并不会产生一个独立的对象单独存在,他只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。这就和我们寻常意义上的复制有所不同了。

对于简单的 object,用 shallow copy 和 deep copy 没区别

复杂的 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 存在呢? 这就是有意思的地方了。

先看一个例子

对于数字和字符串来说,深拷贝和浅拷贝内纯地址都相同

#!/usr/bin/python3

import copy
#浅拷贝copy
#深拷贝deepcopy
a = 1233456
a1 = 1233456
a2 = a
print(id(a))
print(id(a1))
print(id(a2)) print("-----------")
a3 = copy.copy(a)
print(id(a3))

执行结果:

31100688
31100688
31100688
-----------
31100688

#!/usr/bin/python3

import copy
#浅拷贝copy
#深拷贝deepcopy
a = 1233456
a1 = 1233456
a2 = a
print(id(a))
print(id(a1))
print(id(a2)) print("-----deepcopy------")
a3 = copy.deepcopy(a)
print(id(a3))

执行结果:

31035152
31035152
31035152
-----deepcopy------
31035152
#!/usr/bin/python3

import copy
#浅拷贝copy
#深拷贝deepcopy
a = "QQ603374730"
a1 = "QQ603374730"
a2 = a
print(id(a))
print(id(a1))
print(id(a2)) print("-----deepcopy------")
a3 = copy.deepcopy(a)
print(id(a3))

执行结果:

41768368
41768368
41768368
-----deepcopy------
41768368

对于 数字 和 字符串 而言,赋值、浅拷贝和深拷贝无意义,因为其永远指向同一个内存地址。

import copy
# ######### 数字、字符串 #########
n1 = 123
# n1 = "i am alex age 10"
print(id(n1))
# ## 赋值 ##
n2 = n1
print(id(n2))
# ## 浅拷贝 ##
n2 = copy.copy(n1)
print(id(n2)) # ## 深拷贝 ##
n3 = copy.deepcopy(n1)
print(id(n3))

对于字典、元祖、列表 而言,进行赋值、浅拷贝和深拷贝时,其内存地址的变化是不同的。

赋值,只是创建一个变量,该变量指向原来内存地址,如:

n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 456]}

n2 = n1
print(id(n1))
print(id(n2))

执行结果:

31222520
31222520

浅拷贝,在内存中只额外创建第一层数据

import copy

n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 456]}

n3 = copy.copy(n1)
print(id(n1))
print(id(n3))
print(id(n1["k3"]))
print(id(n3["k3"]))
34827000
34827072
42225224
42225224

  

深拷贝,在内存中将所有的数据重新创建一份(排除最后一层,即:python内部对字符串和数字的优化)

import copy

n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 456]}

n4 = copy.deepcopy(n1)
print(id(n1))
print(id(n4))
print(id(n1["k3"]))
print(id(n4["k3"]))

执行结果:

31222520
41688304
41963080
41962504

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的时候到底发生了什么? 
再看一段代码:

>>> 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

学过Docker的人应该对镜像这个概念不陌生,我们可以把镜像的概念套用在copy上面。

概念图如下: 

copy对于一个复杂对象的子对象并不会完全复制,什么是复杂对象的子对象呢?就比如序列里的嵌套序列,字典里的嵌套序列等都是复杂对象的子对象。对于子对象,python会把它当作一个公共镜像存储起来,所有对他的复制都被当成一个引用,所以说当其中一个引用将镜像改变了之后另一个引用使用镜像的时候镜像已经被改变了。

所以说看这里的origin[2],也就是 [3, 4] 这个 list。根据 shallow copy 的定义,在 cop1[2] 指向的是同一个 list [3, 4]。那么,如果这里我们改变了这个 list,就会导致 origin 和 cop1 同时改变。这就是为什么上边 origin[2][0] = “hey!” 之后,cop1 也随之变成了 [1, 2, [‘hey!’, 4]]。

而deepcopy概念图如下: 

deepcopy的时候会将复杂对象的每一层复制一个单独的个体出来。 
这时候的 origin[2] 和 cop2[2] 虽然值都等于 [3, 4],但已经不是同一个 list了。即我们寻常意义上的复制。

Python浅拷贝copy()与深拷贝deepcopy()区别的更多相关文章

  1. Python 浅拷贝copy()与深拷贝copy.deepcopy()

    首先我在这介绍两个新的小知识,要在下面用到.一个是函数 id() ,另一个是运算符 is.id() 函数就是返回对象的内存地址:is 是比较两个变量的对象引用是否指向同一个对象,在这里请不要和 == ...

  2. python(41):copy拷贝(深拷贝deepcopy与浅拷贝copy)

    Python中的对象之间赋值时是按引用传递的,如果需要拷贝对象,需要使用标准库中的copy模块. 1.copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象. 2.copy.deepco ...

  3. 深入理解Python中赋值、深拷贝(deepcopy)、浅拷贝(copy)

    赋值 python跟java中的变量本质是不一样的,Python的变量实质上是一个指针(int型或str型),而java的变量是一个可操作的存储空间. a = 123b = a print(id(a) ...

  4. javascript中的浅拷贝ShallowCopy与深拷贝DeepCopy

    拷贝,在js中,分为浅拷贝和深拷贝.这两者是如何区分的呢?又是如何实现的呢? 深浅拷贝的区分 首先说下,在js中,分为基础数据类型和复杂数据类型, 基础数据类型:Undefined.Null.Bool ...

  5. Python 拷贝对象(深拷贝deepcopy与浅拷贝copy)

    1. copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象. 2. copy.deepcopy 深拷贝 拷贝对象及其子对象 一个很好的例子: # -*-coding:utf-8 -*- ...

  6. Python [拷贝copy / 深度拷贝deepcopy] | 可视化理解

    Python 是一门面向对象的语言, 在Python一切皆对象. 每一个对象都有由以下三个属性组成: ------------------------------------------------- ...

  7. Python - 对象赋值、浅拷贝、深拷贝的区别

    前言 Python 中不存在值传递,一切传递的都是对象的引用,也可以认为是传址 这里会讲三个概念:对象赋值.浅拷贝.深拷贝 名词解释 变量:存储对象的引用 对象:会被分配一块内存,存储实际的数据,比如 ...

  8. 关于python中赋值、浅拷贝、深拷贝之间区别的深入分析

    当重新学习了计算机基础课程<数据结构和算法分析>后再来看这篇自己以前写的博文,发现错误百出.python内置数据类型之所以会有这些特性,归根结底是它采用的是传递内存地址的方式,而不是传递真 ...

  9. Python的copy()与deepcopy()区别

    Python的copy()与deepcopy()分别对应浅拷贝和深拷贝. 它们的理论区别: deepcopy():深复制(也就是寻常意义上的复制),即将被复制对象完全再复制一遍作为独立的新个体单独存在 ...

随机推荐

  1. 模拟+位运算 HDOJ 5491 The Next

    题目传送门 题意:意思很简单,找一个最接近D且比D大的数,满足它的二进制表示下的1的个数在[S1, S2]之间 分析:从D + 1开始,若个数小于S1,那么从低位向高位把0替换成1直到S1就是最小值, ...

  2. Reference for shell scripting

    ${var} 和 $var的区别 http://stackoverflow.com/questions/8748831/when-do-we-need-curly-braces-in-variable ...

  3. 482 License Key Formatting 注册码格式化

    详见:https://leetcode.com/problems/license-key-formatting/description/ C++: class Solution { public: s ...

  4. 164 Maximum Gap 最大间距

    给定一个无序的数组,找出数组在排序后相邻的元素之间最大的差值.尽量尝试在线性时间和空间复杂度情况下解决此问题.若数组元素个数少于2,则返回0.假定所有的元素都是非负整数且范围在32位有符号整数范围内. ...

  5. Delphi7中使用Indy9的IdSmtp发送email时subject过长会出现乱码的解决办法

    procedure TIdMessageClient.SendHeader(AMsg: TIdMessage); var LHeaders: TIdHeaderList; begin LHeaders ...

  6. SQL 导出csv

    https://jingyan.baidu.com/album/4b07be3c466b5d48b280f37f.html?picindex=9

  7. C# 移动开发(Xamarin.Form) Plugin.BLE 蓝牙连接

    随着Xamarin.Form项目接近尾声,仔细一算才发现过来大半年时间了. 期间除了刚开始有闲情写写,现在总算有空来总结一下了. 来先说 Plugin.BLE (https://github.com/ ...

  8. ie 导出不行,不兼容问题,或只出现后缀文件无法识别

    // 下载模板 @RequestMapping("/download") @ResponseBody public ResponseEntity<byte[]> dow ...

  9. 云梯互联:所有主机已全面支持免费SSL!附小白配置教程。

    HTTPS和HTTP的区别:1.HTTPS是加密传输协议,HTTP是名文传输协议;2.HTTPS需要用到SSL证书,而HTTP不用;3.HTTPS比HTTP更加安全,对搜索引擎更友好,利于SEO4. ...

  10. 11gR2集群件任务角色分离(Job Role Separation)简介

       从11gR2版本开始,Oracle推荐使用不同的操作系统用户安装GI和数据库软件,例如:使用grid用户安装GI,使用Oracle用户安装数据库软件.当然,用户还是可以使用Oracle用户安装G ...