Python学习笔记(5):赋值、浅拷贝、深拷贝
2017-05-23 01:27
447 查看
与很多编程语言一样,Python的拷贝方式分为赋值、浅拷贝、深拷贝。
在学习过程中,我对拷贝的了解很模糊。在经过一系列的实验后,我对这三者的概念有了进一步的理解。
赋值是把一个对象的内存地址赋给一个变量,让变量指向该地址( 旧瓶装旧酒。旧瓶指变量, 旧酒指对象的内存地址,旧是指变量和数据产生后就保留在内存中)。
修改不可变类型(str、tuple)需要开辟新的内存地址空间
修改可变类型(list等)不需要开辟新的内存地址空间
我们通过一些例子来分析下赋值操作:
我们可以发现a, b, c三者的id是一样的,以上操作相当于c = a = b = ‘hello’,为什么呢?因为str是不可变类型,所以’hello’只有一个内存地址,a, b, c三者的id一样。赋值是系统先给一个变量或者对象(这里是’hello’)分配了内存,然后再将id赋给a, b, c,所以它们的id是相同的。
变量重新赋值str
这时a的id和值变了,但是b, c的id和值都未变。因为str是不可变类型,不同str占用不同的内存空间。重新赋值a需要新开辟内存空间,再把a指向新的str内存地址,所以当a的str值改变,a指向的内存地址也会改变。b, c由于字符串’hello’的不变性,所以id和值都不会发生改变。
为何a和b的id不同?因为list是可变类型,任意一个list占用不同的内存地址。
修改可变类型list,不需要开辟新的内存地址
a和c的值均改变,id没有改变;变量b的id和值都没有改变。由于list的可变性,所以修改list的值不需要另外开辟空间,只需修改原地址的值,所以a, c的值均改变,id没有改变。
对于数字,字符串和其他原子类型对象等,没有被拷贝的说法。即便是用深拷贝,查看id也是一样的;如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。
从id来看,三者是不同的对象。
奇怪的事情发生了,jack、tom、anny的年龄都变成18了。jack、tom、anny他们应当都是不同的对象,怎么会互相影响呢?看下jack,tom,anny的内部元素每个元素id:
恍然大悟,原来jack、tom、anny的年龄指向的是同一个list元素,修改了其中一个,当然影响其他人了。为了便于理解,我画了一个草图:
利用切片操作和工厂方法(list方法)拷贝叫浅拷贝。它只是拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。
深拷贝后,可以发现jack, tom,anny的id以及元素值均不相同。这才是完全拷贝了一个副本 。
修改jack, tom,anny,它们之间不会互相影响。打印出每个人的内部元素id。
深拷贝后,jack,tom,anny元素id均不同,内部元素也都指向了不同的对象。这是完全拷贝的一个副本,修改jack后,tom没有发生改变,因为tom是一个完全的副本,元素id与jack均不同,jack修改不影响tom。
浅拷贝是在另一块地址中创建一个新的变量或容器,但是容器内的元素的地址均是源对象的元素的地址的拷贝。也就是说新的容器中指向了旧的元素( 新瓶装旧酒 )
深拷贝是在另一块地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的,仅仅是值相同而已,是完全的副本。也就是说( 新瓶装新酒 )
Python中的赋值、浅拷贝、深拷贝介绍
在学习过程中,我对拷贝的了解很模糊。在经过一系列的实验后,我对这三者的概念有了进一步的理解。
1. 赋值
首先,我们要对赋值操作有以下认识:赋值是把一个对象的内存地址赋给一个变量,让变量指向该地址( 旧瓶装旧酒。旧瓶指变量, 旧酒指对象的内存地址,旧是指变量和数据产生后就保留在内存中)。
修改不可变类型(str、tuple)需要开辟新的内存地址空间
修改可变类型(list等)不需要开辟新的内存地址空间
我们通过一些例子来分析下赋值操作:
1.1 字符串赋值
同一str的内存地址唯一,不同str占用不同的内存地址a = 'hello' b = 'hello' c = a print(a, b, c) # hello hello hello [id(x) for x in [a,b,c]] # [4526716328, 4526716328, 4526716328]
我们可以发现a, b, c三者的id是一样的,以上操作相当于c = a = b = ‘hello’,为什么呢?因为str是不可变类型,所以’hello’只有一个内存地址,a, b, c三者的id一样。赋值是系统先给一个变量或者对象(这里是’hello’)分配了内存,然后再将id赋给a, b, c,所以它们的id是相同的。
变量重新赋值str
a = 'world' print(a, b, c) # world hello hello [id(x) for x in [a,b,c]] # [4526651464, 4526716328, 4526716328]
这时a的id和值变了,但是b, c的id和值都未变。因为str是不可变类型,不同str占用不同的内存空间。重新赋值a需要新开辟内存空间,再把a指向新的str内存地址,所以当a的str值改变,a指向的内存地址也会改变。b, c由于字符串’hello’的不变性,所以id和值都不会发生改变。
1.2 列表赋值
任意一个list占用不同的内存地址,无论元素是否相同a = ['hello'] b = ['hello'] c = a print(a, b, c) # ['hello'] ['hello'] ['hello'] [id(x) for x in [a, b, c]] # [4535290824, 4535290888, 4535290824]
为何a和b的id不同?因为list是可变类型,任意一个list占用不同的内存地址。
修改可变类型list,不需要开辟新的内存地址
a.appand(12) print(a, b, c) # ['hello', 12] ['hello'] ['hello', 12] [id(x) for x in [a,b,c]] # [4535290824, 4535290888, 4535290824]
a和c的值均改变,id没有改变;变量b的id和值都没有改变。由于list的可变性,所以修改list的值不需要另外开辟空间,只需修改原地址的值,所以a, c的值均改变,id没有改变。
对于数字,字符串和其他原子类型对象等,没有被拷贝的说法。即便是用深拷贝,查看id也是一样的;如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。
2. 浅拷贝
所谓浅拷贝就是对引用的拷贝。2.1 利用切片操作和工厂方法(list方法)拷贝
代码场景:有一个小伙jack,tom通过切片操作拷贝jack,anny通过工厂方法拷贝jack。jack = ['jack', ['age', 20]] tom = jack[:] anny = list(jack) print(id(jack), id(tom), id(anny)) # 4570999880 4570944008 4570999752
从id来看,三者是不同的对象。
2.2 修改原对象的值
tom[0] = 'tom' anny[0] = 'anny' anny[1][1] = 18 print(jack, tom, anny) # ['jack', ['age', 18]] ['tom', ['age', 18]] ['anny', ['age', 18]] print(id(jack), id(tom), id(anny)) # 4570999880 4570944008 4570999752
奇怪的事情发生了,jack、tom、anny的年龄都变成18了。jack、tom、anny他们应当都是不同的对象,怎么会互相影响呢?看下jack,tom,anny的内部元素每个元素id:
print([id(x) for x in jack]) # [4570490672, 4571499784] print([id(x) for x in tom]) # [4570491232, 4571499784] print([id(x) for x in anny]) # [4570490616, 4571499784]
恍然大悟,原来jack、tom、anny的年龄指向的是同一个list元素,修改了其中一个,当然影响其他人了。为了便于理解,我画了一个草图:
利用切片操作和工厂方法(list方法)拷贝叫浅拷贝。它只是拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。
3. 深拷贝
利用copy中的deepcopy方法进行拷贝就叫做深拷贝,外围和内部元素都进行了拷贝对象本身,而不是引用。它是完全拷贝了一个副本,变量内部元素id都不一样。3.1 copy.deepcopy
我们利用copy模块中的deepcopy方法进行深拷贝。import copy jack = ['jack', ['age', '20']] tom = copy.deepcopy(jack) anny = copy.deepcopy(jack) print([id(x) for x in [jack, tom, anny]]) # [4526691976, 4526690440, 4526692936]。因为list是可变类型,所以jack, tom, anny的id不同 print([id(x) for x in jack]) # [4526653368, 4526690568],'jack'是str不可变类型,['age','20']是list可变类型 print([id(x) for x in tom]) # [4526653368, 4526644488] print([id(x) for x in anny]) # [4526653368, 4526642120]
深拷贝后,可以发现jack, tom,anny的id以及元素值均不相同。这才是完全拷贝了一个副本 。
3.2 修改原数据
tom[0] = 'tom' anny[0] = 'anny' print(jack, tom, anny) # ['jack', ['age', '20']] ['tom', ['age', '20']] ['anny', ['age', 20]] anny[1][1] = 18 tom[1].append(24) print(jack, tom, anny) # ['jack', ['age', '20']] ['tom', ['age', '20',24]] ['anny', ['age', 18]]
修改jack, tom,anny,它们之间不会互相影响。打印出每个人的内部元素id。
print([id(x) for x in [jack, tom, anny]]) # [4526691976, 4526690440, 4526692936] print([id(x) for x in jack]) # [139132064, 3073507244L] print([id(x) for x in tom]) # [139137464, 139132204] print([id(x) for x in anny]) # [139141632, 139157548]
深拷贝后,jack,tom,anny元素id均不同,内部元素也都指向了不同的对象。这是完全拷贝的一个副本,修改jack后,tom没有发生改变,因为tom是一个完全的副本,元素id与jack均不同,jack修改不影响tom。
4. 总结
赋值是把一个对象内存地址赋给一个变量,让变量指向该内存地址( 旧瓶装旧酒 )浅拷贝是在另一块地址中创建一个新的变量或容器,但是容器内的元素的地址均是源对象的元素的地址的拷贝。也就是说新的容器中指向了旧的元素( 新瓶装旧酒 )
深拷贝是在另一块地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的,仅仅是值相同而已,是完全的副本。也就是说( 新瓶装新酒 )
5. 参考文章
python中的深拷贝和浅拷贝理解Python中的赋值、浅拷贝、深拷贝介绍
相关文章推荐
- Python学习笔记——赋值
- Python学习笔记之疑问11:批量赋值
- Python学习笔记之拷贝
- Python学习笔记_浅拷贝,深拷贝
- python学习笔记---元组赋值
- Python学习笔记——可变类型&不可变类型&深拷贝&浅拷贝
- C++学习笔记-类5-浅拷贝和深拷贝以及赋值操作符的重载
- 流畅的python学习笔记第八章:深拷贝,浅拷贝,可变参数
- 【Python 学习手册笔记】赋值、表达式和打印
- python学习笔记:深拷贝,浅拷贝
- Python学习笔记(八):Python语句简介、赋值、表达式和打印
- Python学习笔记--变量和赋值
- python学习笔记七:浅拷贝深拷贝
- python 学习笔记- 变量赋值
- Python 基础学习笔记6- 多变量赋值
- Python学习笔记--变量赋值过程
- Python学习笔记(三)奇妙的赋值
- Python学习笔记-三种赋值操作
- Python学习笔记——深拷贝与浅拷贝
- python学习笔记——浅拷贝与深拷贝