可变类型与不可变类型及其赋值、浅拷贝与深拷贝
可变类型与不可变类型
python变量可以分为两种:可变类型与不可变类型
其中:
不可变类型:数字、字符串、元祖、不可变集合
可变类型:列表、字典、集合、可变集合
每当python声明一个变量,它都以对象的形式存在内存中,可以理解为变量名为对象的指针
1.1 不可变类型的特点
当声明param=1时,则param指向存储1的空间。同时,python使用计数器的方式来判断空间的引用情况,当计数器为0时,会将内存回收。
python在声明不可变类型时,会在已经声明的对象中去寻找该对象是否已经被声明过,若该对象被声明过,变量会直接指向该对象而不会在申请新的内存空间。
(1). 相同值变量指向同一块内存地址
例如,对于数字型变量:
a = 3
b = 3
a is b
True
a 与 b都为不可变类型,当声明b变量时,由于和a相同已经被声明过,则a、b指向同一块内存
(2). 不可对其值直接修改
例如,对于字符串型变量,同样为不可变类型
str_test = "haohaoxuexi"
不可对字符串中某个字母进行替换
替换报错:
但是,可以对其进行切割或加长处理:
加长处理:
切割:
不是在原有内存中存储的字符串值进行改变,本质上是将新的字符串存储到新的内存中
1.2 不可变类型的赋值、浅拷贝与深拷贝
import copy
a = (1, 2)
b1 = a
b2 = copy.copy(a)
b3 = copy.deepcopy(a)
对于不可变类型,赋值、浅拷贝和深拷贝的意义相同(或者说深浅拷贝无意义),毫无区别可言(也就是说不可变类型,无深浅拷贝),不同变量的同一个值,永远指向同一个地址
。值相同,地址也相同,这些变量也就完全相同。
2.1 可变类型的特点
(1). 可变类型变量可以对其值进行修改:
a = [1, 2, 3]
a[0] = 888
a
结果:
[888, 2, 3]
在原有内存中存储的值进行修改,注意与不可变类型不可修改值进行区别,修改值时它不开辟新的内存空间
(2). 相同值变量可以指向同一内存地址,也可指向不同内存地址
可变类型比不可变类型变量要复杂,不可简单判断相同值变量的地址相同或不同,但一般按照如下进行分类分析:赋值、浅拷贝和深拷贝
2.2 可变类型的赋值、浅拷贝与深拷贝
(1). 对于赋值操作,相同值的变量指向同一内存地址
赋值操作不会开辟新的内存空间,而是将a的引用赋给b,a与b指向同一内存地址
例如,对于列表型变量,为可变变量:
a = [1, 2, 3]
b = a
a is b
结果:
变量都是以指针的形式存在,赋值操作其实是将指向的对象进行改变,并不是改变了内存中的内容
(2). 对于浅拷贝,变量部分
指向不同地址,变量的第一层值指向不同地址,而深层指向同一地址
浅拷贝会分配新的内存地址,该地址与原对象( 变量 )地址不是完全不同,而是父对象地址不同,子对象地址相同;即只拷贝了对象的第一层,重新分配了内存地址,而对于深层,如第二层,没有进行拷贝,内存地址相同。
例1,对于简单单层列表型变量,为可变变量:
import copy
a = [1, 2, 3]
b = copy.copy(a)
print(a is b)
print(id(a), id(b))
结果:
没有子层,新变量直接整体开辟新地址
例2,对于嵌套列表变量:
a = [1, 2, 3, [4, 5, 6]]
b = copy.copy(a)
print(a is b)
print(id(a),id(b))
print("第一层:",a[0:3],id(a[0:3]),b[0:3],id(b[0:3]))
print("深层:",a[3],id(a[3]),b[3],id(b[3]))
结果:
可以看到:变量的值相同,第一层地址不同,但是子层地址相同
新变量第一层引用指向新地址,新变量子层引用指向原变量子层内存地址
(3). 对于深拷贝,变量整体
指向不同的地址( 无论有无父子层 )
深拷贝拷贝了对象所有元素,包括多层嵌套的元素
import copy
a = [1, 2, 3, [4, 5, 6]]
b = copy.deepcopy(a)
print(a is b)
print(id(a),id(b))
print("第一层:",a[0:3],id(a[0:3]),b[0:3],id(b[0:3]))
print("深层:",a[3],id(a[3]),b[3],id(b[3]))
结果:
无论哪一层,内存地址都变了,新的拷贝变量拥有全新的地址
注意,下面的例子:
a = [1, 2, 3]
b = [1, 2, 3]
重新赋相同的值,也类似于深拷贝,会分配新的地址,a与b完全无关。