Python学习笔记-深浅拷贝理解

可变(mutable)和不可变(immutable)参数

不可变对象在进行重新赋值的时候,实际上是将原始值丢弃,将变量指向一个新值;可变对象的可变性质是指更改可变对象的子对象,比如list中的item元素的更改。

Python中的string,tuple(元组)和number是不可变对象,而dict,list等是可变对象;当可变对象传入函数中后,如果在函数中对对象的子对象(子元素)进行修改,那么会导致函数外的原始对象也会进行更改;不可变对象不会存在这个问题。


Python学习笔记-深浅拷贝理解_第1张图片
传递不可变参数示例


调用结果


Python学习笔记-深浅拷贝理解_第2张图片
可变参数函数调用示例


结果

可变类型(mutable):列表,字典

不可变类型(unmutable):数字,字符串,元组

不可变类型有什么好处?

    如果数据是不可变类型,当我们把数据传给一个不了解的API时,可以确保我们的数据不会被修改。因此,如果我们要操作一个从函数返回的元组,可以通过内建函数list()把它转换成一个列表。

这里的可变不可变,是指内存中的那块内容(value)是否可以被改变。如果是不可变类型,在对对象本身操作的时候,必须在内存中新申请一块区域(因为老区域不可变)。如果是可变类型,对对象操作的时候,不需要再在其他地方申请内存,只需要在此对象后面连续申请(增加内存/减少内存)即可,也就是它的指向内存存储的位置会保持不变,但区域会变长或者变短,可以使用内建函数id()来确认对象的身份在两次赋值前后是否内存的位置是否发生了变化。

内建函数id() 介绍

id(变量)函数,用于返回对象的身份(identity)。其实,这里所谓的身份,就是该对象的内存地址。id地址可理解为相应变量在存储中的位置;这个存储位置不变是指一个运行周期,在一定数值范围内分配的编号,不同运行周期,即便同一变量存储位置都会有变化,可变对象,一个运行周期内,存储位置上的值是可以改变,不可变对象则相反

print(id(a))

print(hex(id(a)))

在我的计算机上,它们返回的是:

11246696

'0xab9c68' 分别为内存地址的十进制和十六进制表示。


Python学习笔记-深浅拷贝理解_第3张图片
单个运行周期内,整数333、111的存储位置已经被分配固定,且存储的内容是不允许修改的,变量a指向了111,因此a的存储位置就是111的位置,b也是如此,当a指向了333,a指向的存储位置就会发生改变,因此a的id值发生了变化。


Python学习笔记-深浅拷贝理解_第4张图片



Python学习笔记-深浅拷贝理解_第5张图片
这里暂时无法给出合理的解释,但会在下面的深浅拷贝中解释部分,并结合自己的理解,同时能力有限,还需要自己进一步理解python的内存管理


Python学习笔记-深浅拷贝理解_第6张图片
列表,字典可变对象进行赋值时,每次赋值都是分配新的地址,从使用角度来讲,是因为这些对象是可变的,如果分配同一地址,当发生修改时,可能导致赋值对象不一致

    

Python学习笔记-深浅拷贝理解_第7张图片
不可变对象赋值,分配的地址是不变,可以理解为他们不需要修改,分配同一物理地址,可以节省内存开销

深浅拷贝的定义

在Python中对象的赋值其实就是对象的引用。当创建一个对象,把它赋值给另一个变量的时候,python并没有拷贝这个对象,只是拷贝了这个对象的引用而已。

浅拷贝:拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。也就是,把对象复制一遍,但是该对象中引用的其他对象我不复制

深拷贝:外围和内部元素都进行了拷贝对象本身,而不是引用。也就是,把对象复制一遍,并且该对象中引用的其他对象我也复制。

几种拷贝类型的操作

1、赋值

对不可变对象的赋值


Python学习笔记-深浅拷贝理解_第8张图片

        python有一个重用机制,对于同一个数字,python并不会开辟一块新的内存空间,而是维护同一块内存地址,只是将该数字对应的内存地址的引用赋值给变量a和b。所以根据输出结果,a和b其实对应的是同一块内存地址,只是两个不同的引用罢了。同样的,对于c = a,其实效果等同于“c= 1231;”,它也就是将a指向123123的引用赋值给c。字符串跟数字的原理雷同,如果把123123改成“abcabc”也是一样的。

结论:对于通过用 = 号赋值,数字,字符串等不可变对象(元组除外)在内存当中用的都是同一块地址

对元组对象的赋值


Python学习笔记-深浅拷贝理解_第9张图片

结论:对于通过用 = 号赋值 ,每次分配的是不同的物理地址,这和字典,列表的方式是一样的

对可变对象赋值


Python学习笔记-深浅拷贝理解_第10张图片

结论:对可变对象如元组,字典等进行赋值时,实际上是分配不同的物理地址,但list3=list1,list3实际上分配的物理地址是和list1一致的,只是把list1的引用拿过来赋值给list3而已

2、浅拷贝:

进行浅拷贝时,需要引用import copy包,部分对象如列表,字典等是有内置的copy函数的

对不可变对象的浅拷贝


Python学习笔记-深浅拷贝理解_第11张图片
对不可变对象的浅拷贝,实际上只是把a的引用复制给b,a和b的内存地址还是一样的


对可变对象的浅拷贝


Python学习笔记-深浅拷贝理解_第12张图片


Python学习笔记-深浅拷贝理解_第13张图片
通过以上结果可以看出,进行浅拷贝时,我们的字典第一层n1和n3指向的内存地址(相当于上图的红色区域)已经改变了,但是对于第二层里的列表并没有拷贝,它的内存地址还是一样的,对于内置函数copy而言,n2和n3在性质上是一样的。这里还要注意最底层的元素,其实物理位置是不变的

深拷贝:

进行深拷贝时,需要引用import copy包,部分对象如列表,字典等是有内置的deepcopy函数的 


Python学习笔记-深浅拷贝理解_第14张图片


Python学习笔记-深浅拷贝理解_第15张图片
字典里面的第一层和里面嵌套的地址都已经变了。对于深拷贝,它会拷贝多层,将第二层的列表也拷贝一份,如果还有第三层嵌套,那么第三层的也会拷贝,但是对于里面的最小元素,比如数字和字符串,这里就是“wu”,123,“alex”,678之类的,按照python的机制,它们会共同指向同一个位置,它的内存地址是不会变的。

结论: 对于深拷贝,字典、列表、元组等类型,它里面嵌套多少层,就会拷贝多少层出来,但是最底层的数字和字符串地址不变。

切片


Python学习笔记-深浅拷贝理解_第16张图片
可以验证切片属于浅拷贝的一种

总结:

1,切片可以应用于:列表、元组、字符串,但不能应用于字典,切片属于浅拷贝。

2、深浅拷贝,既可应用序列(列表、元组、字符串),也可应用字典。

3、对于不可变对象的深浅拷贝

不可变对象类型,没有被拷贝的说法,即便是用深拷贝,查看id的话也是一样的,如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。

一句话就是,不可变类型,不管是深拷贝还是浅拷贝,地址值和拷贝后的值都是一样的。

4、对可变对象的深浅拷贝

=浅拷贝:值相等,地址相等 

copy浅拷贝:值相等,地址不相等 

deepcopy深拷贝:值相等,地址不相等

你可能感兴趣的:(Python学习笔记-深浅拷贝理解)