赞
踩
python中的对象包含三个属性,id、type和value,id代表着对象唯一的标识符,是独一无二的,cpython中代表了存放对象的内存地址;type代表着对象的类型,比如说数字1的type就是int,字符串‘abc’的type就是str,这里还可以进一步去区分type()函数与isinstance()函数的区别,简单来说type函数不考虑继承,不会认为子类的对象属于父类,而isinstance函数考虑继承;value就是代表我们赋给对象的值。
深拷贝和浅拷贝来自于python的copy模块,可以通过import copy来导入该模块。
浅拷贝的语法为copy.copy(),深拷贝的语法为copy.deepcopy()。
在高级语言中,变量的存储方式有两个,值语义和引用语义。
在python中,万物皆对象,采用的就是引用语义,赋给变量的值作为对象拥有自己的存储空间,赋值给变量,变量中存储的只是值对象的内存地址,而不是变量本身。
python中的对象可以简单的分为可变对象和不可变对象,基本数据类型比如int,float,bool是不可变的(python2中还有long),结构数据类型中,tuple,str是不可变对象,list,dict,set是可变对象,之所以是可变的,是因为变量中存储的是值对象的地址,值的地址里面存放的是元素值的地址,可以一直这样链式传递下去,所以我们对其进行内置操作(比如append,pop,remove等)都不会改变变量中存储的地址,也就表现为对象是可变的,比如说,a=[1, 2, 3],a存储的是对象[1, 2, 3]的地址,[1, 2, 3]中存储的是元素1,2,3的地址,因此要注意的是仅限于python的内置操作,别的操作比如赋值操作会改变变量的引用,即使与原来的数据一样。
a = [1, 2, 3] print(id(a)) a = [1, 2, 3] print(id(a)) a.append(4) print(id(a), a) a.pop(0) print(id(a), a) b = [7, 8, 9] print(id(b)) 输出结果: 6054472 6054536 6054536 [1, 2, 3, 4] 6054536 [2, 3, 4] 6054472
上述结果体现了python内置操作不会改变对象的引用,赋值操作会改变对象的引用,但是如果是c = a,那就没问题了,因为a本来就是存储着值对象的地址,赋值操作后,传递给c的实际上也是值对象的地址,所以id并不会变,有意思的是最后的b的id竟然与第一个a的id一样,这说明了当变量重新指向新的内存地址后,之前指向的内存就会被回收,建立新的变量时,又从程序自己的内存空间空间开始位置查找可分配的内存。
下面再看一个有意思的操作:
a = [1, 2, 3] print(id(a)) a[0] = 4 print(id(a)) a[2] = [1, 2, 3] print(id(a)) 输出结果: 5071432 5071432 5071432 对列表中的元素进行赋值操作,并没有改变变量中存储的地址,但是按照前面的说法,赋值操作会改变变量指向的地址,这不是前后矛盾吗? 实际上并不矛盾,个人的理解是这样的: 前面我们也提到了,对于list这种复杂的数据结构,变量只是值对象的引用,存储值对象的地址,值对象中存储着各个元素的地址,因此,我们对元素进行赋值操作只是修改值对象中存储的单个的元素地址,并不涉及变量对值对象引用这一层面,所以变的只是元素的地址,变量的id不会变。 a = [1, 2, 3] print(id(a), id(a[0]), id(a[1])) a[0] = 4 print(id(a), id(a[0]), id(a[1])) a[1] = [3, 5, 6] print(id(a), id(a[0]), id(a[1])) 输出结果: 30237256 8791455224864 8791455224896 30237256 8791455224960 8791455224896 30237256 8791455224960 30237320
对列表a中的元素进行赋值操作,会发现对象a的id没变,但被改变的元素的id变了。
明白了对象的存储方式,赋值,浅拷贝和深拷贝的区别就显而易见了
对于不可变对象,每次赋值都会对变量重新初始化,改变其指向的内存地址。改变其中的一个,并不会改变另一个。
a = 1
b = a
print(a, b, id(a), id(b), id(1))
b = 2
print(a, b, id(a), id(b))
输出结果:
1 1 8791455224864 8791455224864 8791455224864
1 2 8791455224864 8791455224896
对于第一次输出中,id(a),id(b),id(1)相等的情况,个人理解与python的内存管理有关,对于不可变数据类型,变量就是值对象地址的另一种表述方式,不会为这个变量名字单独开辟地址空间存储,因为对于不可变对象来说,只要改变值,地址一定会变,因此没必要专门为变量名开辟地址空间。
相比于不可变对象,变量与各元素值对象之间多了一层对各元素值对象的引用,这时变量名会有自己的存储空间,与值对象的id不同。
c = [7, 8, 9] print(id(c), id([7, 8, 9])) c[1] = [123, 456] print(id(c)) 输出结果: 39478024 31499656 39478024 a = [1, 2, 3] b = a print(id(a), id(b), id([1, 2, 3])) b[0] = 4 print(a, b, id(a), id(b)) b = [4, 5, 6] print(a, b, id(a), id(b)) 输出结果: 30630472 30630472 30630536 [4, 2, 3] [4, 2, 3] 30630472 30630472 [4, 2, 3] [4, 5, 6] 30630472 30630536
赋值操作后,只改变b内部变量的值,a与b同时改变,a与b的id不变,如果直接将值对象赋给b,此时b就成为了一个全新的对象。
注意:只有可变对象才有深浅拷贝之分,不可变对象都是一样的
首先要明白为什么有了赋值操作还要拷贝操作?
在程序中,我们经常要对数据进行修改,比如有一个数组list1 = [1, 2, …],要修改list1中的数据,如果原数据在程序后面用不到了,可知直接在list1中修改,但如果原数据还有用,就不能直接在原数据中修改,要另外生成一个数组元素相同的新的对象,如果直接用list2 = list1生成,修改list2,list1也会改变,与我们的初衷不符,直接复制操作的话,list2 = [1, 2, …],数据少的话还可以,数据很多会非常麻烦,这时候,引入拷贝操作,list2 = copy.copy(list1),非常方便而且安全。
浅拷贝:直将第一层的元素进行拷贝,重新生成地址引用,原对象和拷贝对象是两个不同的对象,但是第二层的元素的地址引用还是相同的,因此修改第一层元素只有被修改的变量会变,但修改第二层元素,两个变量的值都会变。
```python import copy a = [1, 2, 3, [4, 5, 6]] b = copy.copy(a) print(a, id(a)) print(b, id(b), end='\n\n') b[1] = 7 print(a) print(b, end='\n\n') b[3].append(8) print(a, id(a)) print(b, id(b)) 输出结果: [1, 2, 3, [4, 5, 6]] 39157448 [1, 2, 3, [4, 5, 6]] 39215368 [1, 2, 3, [4, 5, 6]] [1, 7, 3, [4, 5, 6]] [1, 2, 3, [4, 5, 6, 8]] 39157448 [1, 7, 3, [4, 5, 6, 8]] 39215368
既然浅拷贝是只拷贝一层,那么深拷贝当然就是完全的拷贝,不管对象里面嵌套了基层数据结构,直接拷贝到元素为不可变对象时才结束,这样拷贝出来的新对象就和原对象完全没有关系,无论如何修改,都不会对另一个起到影响。
import copy a = [1, 2, 3, [4, 5, [6, 7, 8]]] b = copy.deepcopy(a) print(a, id(a)) print(b, id(b), end='\n\n') b[1] = 7 print(a) print(b, end='\n\n') b[3].append(9) print(a) print(b, end='\n\n') b[3][2].append(10) print(a, id(a)) print(b, id(b)) 输出结果: [1, 2, 3, [4, 5, [6, 7, 8]]] 39346440 [1, 2, 3, [4, 5, [6, 7, 8]]] 39346888 [1, 2, 3, [4, 5, [6, 7, 8]]] [1, 7, 3, [4, 5, [6, 7, 8]]] [1, 2, 3, [4, 5, [6, 7, 8]]] [1, 7, 3, [4, 5, [6, 7, 8], 9]] [1, 2, 3, [4, 5, [6, 7, 8]]] 39346440 [1, 7, 3, [4, 5, [6, 7, 8, 10], 9]] 39346888
python中对于list有一个魔术方法,简单实用且功能强大,就是切片操作,切片操作可以从目标对象中切取部分或全部的值,那么问题来了,切片操作后的数组与原数组是什么关系,切片操作属于赋值?浅拷贝?深拷贝?
a = [1, 2, [3, 4, 5]] b = a[:] print(a, id(a)) print(b, id(b), end='\n\n') a[1] = 6 print(a, id(a)) print(b, id(b), end='\n\n') a[2].append(7) print(a, id(a)) print(b, id(b), end='\n\n') 输出结果: [1, 2, [3, 4, 5]] 30433928 [1, 2, [3, 4, 5]] 39216072 [1, 6, [3, 4, 5]] 30433928 [1, 2, [3, 4, 5]] 39216072 [1, 6, [3, 4, 5, 7]] 30433928 [1, 2, [3, 4, 5, 7]] 39216072
根据输出结果来看,切片操作后,两个对象的id不同,说明切片操作不是赋值,当改变其中一个的第一层元素时,另一个的元素值不变,但是当改变第二层元素时,两个对象的第二层元素都改变了,至此,我们知道了,切片操作是浅拷贝。
本片文章就分享到这了,有任何问题可以在下方评论。
更多关于内容可以移步我的我的博客,热爱生活,分享知识。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。