预备知识一——python的变量及其存储
在详细的了解python中赋值、copy和deepcopy之前,我们还是要花一点时间来了解一下python内存中变量的存储情况。
在高级语言中,变量是对内存及其地址的抽象。对于python而言,python的一切变量都是对象,变量的存储,采用了引用语义的方式,存储的只是一个变量的值所在的内存地址,而不是这个变量的只本身。
引用语义:
在python中,变量保存的是对象(值)的引用,我们称为引用语义。采用这种方式,变量所需的存储空间大小一致,因为变量只是保存了一个引用。也被称为对象语义和指针语义。
值语义:
有些语言采用的不是这种方式,它们把变量的值直接保存在变量的存储区里,这种方式被我们称为值语义,例如C语言,采用这种存储方式,每一个变量在内存中所占的空间就要根据变量实际的大小而定,无法固定下来。
值语义和引用语义的区别:
值语义: 死的、 傻的、 简单的、 具体的、 可复制的
引用语义: 活的、 聪明的、 复杂的、 抽象的、 不可复制的
预备知识二——各基本数据类型的地址存储及改变情况
由于python中的变量都是采用的引用语义,数据结构可以包含基础数据类型,导致了在python中数据的存储是下图这种情况,每个变量中都存储了这个变量的地址,而不是值本身;对于复杂的数据结构来说,里面的存储的也只只是每个元素的地址而已。:
1.数据类型重新初始化对python语义引用的影响
变量的每一次初始化,都开辟了一个新的空间,将新内容的地址赋值给变量。对于下图来说,我们重复的给str1赋值,其实在内存中的变化如下右图:
从上图我们可以看出,str1在重复的初始化过程中,是因为str1中存储的元素地址由'hello world'的地址变成了'new hello world'的。
2.数据结构内部元素变化重对python语义引用的影响
当对列表中的元素进行一些增删改的操作的时候,是不会影响到lst1列表本身对于整个列表地址的,只会改变其内部元素的地址引用。可是当我们对于一个列表重新初始化(赋值)的时候,就给lst1这个变量重新赋予了一个地址,覆盖了原本列表的地址,这个时候,lst1列表的内存id就发生了改变。上面这个道理用在所有复杂的数据类型中都是一样的。
变量的赋值
我们刚刚已经知道,str1的再次初始化(赋值)会导致内存地址的改变,从上图的结果我们可以看出修改了str1之后,被赋值的str2从内存地址到值都没有受到影响。
看内存中的变化,起始的赋值操作让str1和str2变量都存储了‘hello world’所在的地址,
重新对str1初始化,使str1中存储的地址发生了改变,指向了新建的值,此时str2变量存储的内存地址并未改变,所以不受影响。
2.复杂的数据结构中的赋值
刚刚我们看了简单数据类型的赋值,现在来看复杂数据结构变化对应内存的影响。
上图对列表的增加修改操作,没有改变列表的内存地址,lst1和lst2都发生了变化。
对照内存图我们不难看出,在列表中添加新值时,列表中又多存储了一个新元素的地址,而列表本身的地址没有变化,所以lst1和lst2的id均没有改变并且都被添加了一个新的元素。
简单的比喻一下,我们出去吃饭,lst1和lst2就像是同桌吃饭的两个人,两个人公用一张桌子,只要桌子不变,桌子上的菜发生了变化两个人是共同感受的
初识拷贝
我们已经详细了解了变量赋值的过程。对于复杂的数据结构来说,赋值就等于完全共享了资源,一个值的改变会完全被另一个值共享。
然而有的时候,我们偏偏需要将一份数据的原始内容保留一份,再去处理数据,这个时候使用赋值就不够明智了。python为这种需求提供了copy模块。提供了两种主要的copy方法,一种是普通的copy,另一种是deepcopy。我们称前者是浅拷贝,后者为深拷贝。
深浅拷贝一直是所有编程语言的重要知识点,下面我们就从内存的角度来分析一下两者的区别。
1、什么是深浅拷贝 深拷贝和浅拷贝,python中一切皆对象,像数字,字符串,元祖,如果在内存中存储了这些元素,那么这块内存中的值是不可以被修改的,但是还存在一些可变对象,比如列表和字典,他们所占有的内存空间是可以修改的,有因为python中使用的是引用计数去节省内存。
首先我们定义一个列表并引用
list=[1,2,[1,2]]
list1=list
id(list) 140150690045832
id(list1) 140150690045832
注:id函数的意思是返回对象object的标识符,标识符类型为整数,在同一个时间里所有对象的标识符是唯一的,
如果在不同生命周期的对象有可能有相同的标识符。 例如:我们把list中的第一个数改变的话我们发现list1也随之改变,
问什么会出现这种情况呢,这是因为python中使用的是引用计数去节省内存。
list[0]='a'
list['a',2,[1,2]]
list1['a',2,[1,2]]
但是我们往往只想修改list而不改变list1,怎么实现呢我们可以通过切片拷贝
dellist1
list['b',2,[1,2]]
list1=list[:]#切片赋值
list1['b',2,[1,2]]
list[-1][0]='c'#将list[-1][0]修改为字符c
list['b',2,['c',2]]
list1['b',2,['c',2]] #我们发现虽然我们切片操作了, 但是对于里层的列表却没改变
总结一下:当你使用切片操作拷贝的时候,我们修改了外层的列表,外层不随之改变,但是 修改里层的子列表时,却会改变。 除此之外浅拷贝还有第二种方式,及插入copy模块copy模块实现浅拷贝 首先我们删除列表,并插入模块
dellist
dellist1
importcopy定义列表并对外层内层赋值
list=[1,2,[1,2]]
list1=copy.copy(list)#使用copy模块实现赋值
list[1,2,[1,2]]
list1[1,2,[1,2]]
list[-1][0]='a'#里层赋值
list[0]='b'#外层赋值
list['b',2,['a',2]]
list1[1,2,['a',2]] #这里我们发现外层没有随之改变,但是里层却跟着变了,
这就是copy模块实现浅拷贝,但是我们想实现互不影响那我们使用深拷贝
3、深拷贝 使用deepcopy函数 首先我们删除list1并重新赋值
dellist1
list['b',2,['a',2]]
list1=copy.deepcopy(list)
list['b',2,['a',2]]
list1['b',2,['a',2]]
list[0]='c'#对list[0]修改,修改的是外层
list[-1][0]='d' #对list[-1][0]修改,修改的是内层
list['c',2,['d',2]]
list1['b',2,['a',2]]#这里我们发现对list赋值后,list内层外层都没有随之改 变这就是深拷贝。
4、总结:
浅拷贝:不会拷贝数据中的子对象切片拷贝,
importcopy copy.copy()
深拷贝:会拷贝数据中的子对象
importcopy.deepcopy