深浅copy

和很多语言一样,Python中也分为简单赋值、浅拷贝、深拷贝这几种“拷贝”方式。

在学习过程中,一开始对浅拷贝理解很模糊。不过经过一系列的实验后,我发现对这三者的概念有了进一步的了解。

一、赋值

赋值算是这三种操作中最常见的了,我们通过一些例子来分析下赋值操作:

str例

 >>> a = 'hello'
>>> b = 'hello'
>>> c = a
>>> [id(x) for x in a,b,c]
[4404120000, 4404120000, 4404120000]

由以上指令中,我们可以发现a, b, c三者的地址是一样的。所以以上赋值的操作就相当于c = a = b = 'hello'。

赋值是系统先给一个变量或者对象(这里是'hello')分配了内存,然后再将地址赋给a, b, c。所以它们的地址是相同的。

list例

 >>> a = ['hello']
>>> b = ['hello']
>>> c = a
>>> [id(x) for x in a,b,c]
[4403975952, 4404095096, 4403975952]

但是这种情况却不一样了,a和b的地址不同。为何?

因为str是不可变的,所以同样是'hello'只有一个地址,但是list是可变的,所以必须分配两个地址。

这时,我们希望探究以上两种情况如果 修改值 会如何?

str例

 >>> a = 'world'
>>> [id(x) for x in a,b,c]
[4404120432, 4404120000, 4404120000]
>>> print a, b, c
world hello hello

这时a的地址和值变了,但是b, c地址和值都未变。因为str的不可变性,a要重新赋值则需重新开辟内存空间,所以a的值改变,a指向的地址改变。b, c由于'hello'的不变性,不会发生改变。

list例

 >>> a[0] = 'world'
>>> [id(x) for x in a,b,c]
[4403975952, 4404095096, 4403975952]
>>> print a, b, c
['world'] ['hello'] ['world']

这时a, c的值和地址均改变,但二者仍相同,b不改变。由于list的可变性,所以修改list的值不需要另外开辟空间,只需修改原地址的值。所以a, c均改变。

在了解了以上的不同点之后,我们就能很好地分析浅拷贝和深拷贝了。

我们均用list作为例子。

二、浅拷贝

 >>> a = ['hello', [123, 234]]
>>> b = a[:]
>>> [id(x) for x in a,b]
[4496003656, 4496066752]
>>> [id(x) for x in a]
[4496091584, 4495947536]
>>> [id(x) for x in b]
[4496091584, 4495947536]

Line3,4可以看出a, b地址不同,这符合list是可变的,应开辟不同空间。那浅拷贝就是拷贝了一个副本吗?再看Line5 - 8,我们发现a, b中元素的地址是相同的。如果说字符串'hello'地址一致还能理解,但是第二个元素是list地址仍一致。 这就说明了浅拷贝的特点,只是将容器内的元素的地址复制了一份 。

接着我们尝试修改a, b中的值:

 >>> a[0] = 'world'
>>> a[1].append(345)
>>> print 'a = ', a, '\n\r', 'b = ', b
a = ['world', [123, 234, 345]]
b = ['hello', [123, 234, 345]]

a中第一个元素str改变,但是b中未改变;a中第二个元素改变,b中也改变。这就符合不可变的对象修改会开辟新的空间,可变的对象修改不会开辟新空间。也进一步证明了 浅拷贝仅仅是复制了容器中元素的地址 。

三、深拷贝

 >>> from copy import deepcopy
>>> a = ['hello', [123, 234]]
>>> b = deepcopy(a)
>>> [id(x) for x in a, b]
[4496066824, 4496066680]
>>> [id(x) for x in a]
[4496091584, 4496067040]
>>> [id(x) for x in b]
[4496091584, 4496371792]

深拷贝后,可以发现a, b地址以及a, b中元素地址均不同。这才是完全 拷贝了一个副本 。

修改a的值后:

 >>> a[0] = 'world'
>>> a[1].append(345)
>>> print 'a = ', a, '\n\r', 'b = ', b
a = ['world', [123, 234, 345]]
b = ['hello', [123, 234]]

从Line4,5中可以发现仅仅a修改了,b没有任何修改。 因为b是一个完全的副本,元素地址均与a不同,a修改,b不受影响 。

总结:

1. 赋值是将一个对象的地址赋值给一个变量,让变量指向该地址( 旧瓶装旧酒 )。

2. 浅拷贝是在另一块地址中创建一个新的变量或容器,但是容器内的元素的地址均是源对象的元素的地址的拷贝。也就是说新的容器中指向了旧的元素( 新瓶装旧酒 )。

3. 深拷贝是在另一块地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的,仅仅是值相同而已,是完全的副本。也就是说( 新瓶装新酒 )。

 import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象 b = a #赋值,传对象的引用
c = copy.copy(a) #对象拷贝,浅拷贝
d = copy.deepcopy(a) #对象拷贝,深拷贝 a.append(5) #修改对象a
a[4].append('c') #修改对象a中的['a', 'b']数组对象 print 'a = ', a
print 'b = ', b
print 'c = ', c
print 'd = ', d 输出结果:
a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c = [1, 2, 3, 4, ['a', 'b', 'c']]
d = [1, 2, 3, 4, ['a', 'b']]

一、数字和字符串

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

 1 import copy
2 # ######### 数字、字符串 #########
3 n1 = 123
4 # n1 = "i am alex age 10"
5 print(id(n1))
6 # ## 赋值 ##
7 n2 = n1
8 print(id(n2))
9 # ## 浅拷贝 ##
10 n2 = copy.copy(n1)
11 print(id(n2))
12
13 # ## 深拷贝 ##
14 n3 = copy.deepcopy(n1)
15 print(id(n3))

二、其他基本数据类型

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

1、赋值

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

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

  

2、浅拷贝

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

1 import copy
2
3 n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 456]}
4
5 n3 = copy.copy(n1) 

3、深拷贝

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

1 import copy
2
3 n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 456]}
4
5 n4 = copy.deepcopy(n1)

为什么要拷贝?

当进行修改时,想要保留原来的数据和修改后的数据

数字字符串 和 集合 在修改时的差异?(深浅拷贝不同的终极原因)

1 在修改数据时:
2 数字字符串:在内存中新建一份数据
3 集合:修改内存中的同一份数据

对于集合,如何保留其修改前和修改后的数据?

在内存中拷贝一份

对于集合,如何拷贝其n层元素同时拷贝?

深拷贝

 1 浅copy
2 >>> dict = {"a":("apple",),"bo":{"b":"banna","o":"orange"},"g":["grape","grapefruit"]}
3 >>> dict = {"a":("apple",),"bo":{"b":"banna","o":"orange"},"g":["grape","grapefruit"]}
4 >>> dict2 = dict.copy()
5
6
7 >>> dict["g"][0] = "shuaige" #第一次我修改的是第二层的数据
8 >>> print dict
9 {'a': ('apple',), 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['shuaige', 'grapefruit']}
10 >>> print dict2
11 {'a': ('apple',), 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['shuaige', 'grapefruit']}
12 >>> id(dict["g"][0]),id(dict2["g"][0])
13 (140422980581296, 140422980581296) #从这里可以看出第二层他们是用的内存地址
14 >>>
15
16
17 >>> dict["a"] = "dashuaige" #注意第二次这里修改的是第一层
18 >>> print dict
19 {'a': 'dashuaige', 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['shuaige', 'grapefruit']}
20 >>> print dict2
21 {'a': ('apple',), 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['shuaige', 'grapefruit']}
22 >>>
23 >>> id(dict["a"]),id(dict2["a"])
24 (140422980580816, 140422980552272) #从这里看到第一层他们修改后就不会是相同的内存地址了!
25 >>>
26
27
28 #这里看下,第一次我修改了dict的第二层的数据,dict2也跟着改变了,但是我第二次我修改了dict第一层的数据dict2没有修改。
29 说明:浅copy只是第一层是独立的,其他层面是公用的!作用节省内存
30
31 深copy
32
33 >>> import copy #深copy需要导入模块
34 >>> dict = {"a":("apple",),"bo":{"b":"banna","o":"orange"},"g":["grape","grapefruit"]}
35 >>> dict2 = copy.deepcopy(dict)
36 >>> print dict
37 {'a': ('apple',), 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['grape', 'grapefruit']}
38 >>> print dict2
39 {'a': ('apple',), 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['grape', 'grapefruit']}
40 >>> dict["g"][0] = "shuaige" #修改第二层数据
41 >>> print dict
42 {'a': ('apple',), 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['shuaige', 'grapefruit']}
43 >>> print dict2
44 {'a': ('apple',), 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['grape', 'grapefruit']}
45 >>> id(dict["g"][0]),id(dict2["g"][0])
46 (140422980580816, 140422980580288) #从这里看到第二个数据现在也不是公用了
47
48 # 通过这里可以看出他们现在是一个完全独立的,当你修改dict时dict2是不会改变的因为是两个独立的字典!

python基础知识5——赋值与深浅拷贝——整数和字符串,列表元组字典的更多相关文章

  1. Python第三天 序列 数据类型 数值 字符串 列表 元组 字典

    Python第三天 序列  数据类型  数值  字符串  列表  元组  字典 数据类型数值字符串列表元组字典 序列序列:字符串.列表.元组序列的两个主要特点是索引操作符和切片操作符- 索引操作符让我 ...

  2. Python第三天 序列 5种数据类型 数值 字符串 列表 元组 字典 各种数据类型的的xx重写xx表达式

    Python第三天 序列  5种数据类型  数值  字符串  列表  元组  字典 各种数据类型的的xx重写xx表达式 目录 Pycharm使用技巧(转载) Python第一天  安装  shell ...

  3. 【python】变量的赋值、深浅拷贝

    python——赋值与深浅拷贝 https://www.cnblogs.com/Eva-J/p/5534037.html 啥都不说,看这个博主的文章!

  4. python基础知识-7-内存、深浅、文件操作

    python其他知识目录 1.一些对内存深入理解的案例 以下列举列表,列表/字典/集合这些可变类型都是一样的原理 变量是个地址,指向存储数据的内存空间的地址,它的实质就相当于c语言里的指针.变量和数据 ...

  5. python基础-3 集合 三元运算 深浅拷贝 函数 Python作用域

    上节课总结 1 运算符 in 字符串 判断  : “hello” in "asdasfhelloasdfsadf" 列表元素判断:"li" in ['li', ...

  6. Python自动化开发 - 字符串, 列表, 元组, 字典和和文件操作

    一.字符串 特性:字符串本身不可修改,除非字符串变量重新赋值.Python3中所有字符串都是Unicode字符串,支持中文. >>> name  = "Jonathan&q ...

  7. python字符串/列表/元组/字典之间的相互转换(5)

    一.字符串str与列表list 1.字符串转列表 字符串转为列表list,可以使用str.split()方法,split方法是在字符串中对指定字符进行切片,并返回一个列表,示例代码如下: # !usr ...

  8. Python 整数 长整数 浮点数 字符串 列表 元组 字典的各种方法

    对于Python, 一切事物都是对象,对象基于类创建!! 注:查看对象相关成员var,type, dir 一.整数 如: 18.73.84 每一个整数都具备如下需要知道的功能: def bit_len ...

  9. Python笔记【5】_字符串&列表&元组&字典之间转换学习

    #!/usr/bin/env/python #-*-coding:utf-8-*- #Author:LingChongShi #查看源码Ctrl+左键 #数据类型之间的转换 Str='www.baid ...

随机推荐

  1. pjax技术的应用

    一.什么是PJAX? 现在有一些网站(apicloud,  github)支持这样一种浏览方式,当你点击站内的一个连接的时候,不是传统的跳转到另外一个连接,而是类似ajax的局部刷新改变页面内容,但是 ...

  2. JavaServer Faces 2.0 can not be installed解决方案

    问题描述:maven项目出现如下错误 JavaServer Faces 2.0 requires Dynamic Web Module 2.5 or newer..Maven Java EE Conf ...

  3. 在Linux上编写C#程序

    自从C#开源之后,在Linux编写C#程序就成了可能.Mono-project就是开源版本的C#维护项目.在Linux平台上使用的C#开发工具为monodevelop.安装方式如下: 首先需要安装一些 ...

  4. javascript属性标签

  5. 无需Try catch 的UI事件封装类

    在UI处理中,经常需要进行异常处理,以便在错误发生时能够进行一些自定义的操作,比如,弹出消息框给用户,进行重试操作,记录日志等,如果能够让用户写代码时不用写try...catch,而只是关注业务逻辑的 ...

  6. c++容器

    1.vector:实质是动态堆数组,连续存储的内存区域,支持快速的随机访问. 2.list:实质是双向循环链表,支持在中间进行快速的插入删除,但是不能支持快速的随机访问.非连续的内存区域. 3.deq ...

  7. C语言fmod()函数:对浮点数取模(求余)

    头文件:#include <math.h> fmod() 用来对浮点数进行取模(求余),其原型为:    double fmod (double x); 设返回值为 ret,那么 x = ...

  8. 新增资产时YTD折旧与累计折旧录入错误如何处理

    如新增资产时YTD折旧与累计折旧录入错误,但资产已入账处理,如何处理: 1.需要先报废资产: 2.需要在总账手工帐冲销未冲抵凭证: 3.重新增加资产,录入资产时YTD折旧及累计折旧金额应为0.  

  9. Nopcommerce 二次开发1 基础

    1  Doamin    酒店 namespace Nop.Core.Domain.Hotels { /// <summary> /// 酒店 /// </summary> p ...

  10. 剑指Offer:解决难题时的三大方法

    1.画图 让抽象的东西变得直观生动起来.比如设计二叉树,链表,栈,队列这些数据结构时. 2.举例子 同样可以化抽象为直观.能够更清晰的展现思路.从例子归纳出一般做法. 3.分解 有时问题本身是比较复杂 ...