联想说 Python 基础 15 - 拷贝
可变与不可变类型
首先,什么是可变与不可变类型?可变与不可变说的又是什么?
- 可变类型主要有:列表、字典、集合
- 不可变类型主要有:字符串、元祖、数值
而这些类型所说的变是说这个类型的变量是否可以改变。但是一般情况下,定义了变量之后,不是都可以改变吗,甚至可以重新赋值,那不是都是可以改变的?
id
这里要引入一个函数 - id(),它可以查看变量的 id 值,你可以认为是 身份证账号。
一个身份证账号代表一个人,当身份证账号改变,就算姓名一样,都说明换了一个人,这种就是不可变了。因为换了件衣服,你家狗就不是你的了,你说它可变还是不可变,那肯定不可变了。
所以可变是说,当可变类型的值改变时,id 值不会发生改变。
不可变则是,当可变类型的值发生改变时,id 值会发生改变。
来看看 id() 的用法:
print(id(10)) # 140737163486280
它得到的是一串整数值,如果值一样说明是 “同一个人的不同名称”。
可变类型
前面说到可变类型主要有 列表、字典、集合,这里测试一下是否真的有改变:
lt = [1, 2, 3, 4, 5, 6]
print(id(lt)) # 1986238699840
lt.append(7)
print(id(lt)) # 1986238699840
dic = {'name': "alice", "age": 16}
print(id(dic)) # 1986238698816
dic['sex'] = 'girl'
print(id(dic)) # 1986238698816
st = {1, 2, 3, 4, 5, 6}
print(id(st)) # 1986239565376
st.add(7)
print(id(st)) # 1986239565376
从结果看,这几种类型在改变了其中的元素后,id 值都没有改变,所以它们确实是可变的。
不可变类型
接着再测试几个不可变类型 - 字符串、元祖、数值:
num = 10
print(id(10)) # 140737163486280
print(id(num)) # 140737163486280
num += 1
print(id(num)) # 140737163486312
print(id(11)) # 140737163486312
str1 = 'abc'
print(id(str1)) # 140737162120992
str1 = 'abc' + 'q'
print(id(str1)) # 2197512541616
tp = (1, 2, 3, 4, 5, 6)
print(id(tp)) # 1986242730048
元祖就不用多说了,根本没有提供可修改的方法。而整形和字符串一旦修改,id 值就发生了变化,所以它们是不可变的。
拷贝
前面我们都是用的赋值,也就是 =。这里说的拷贝(复制)与之类似,但是功能上面会有一些区别,后面则主要讨论拷贝中的深浅拷贝。
我们在谈论拷贝时,其实谈论的主要对象都是基于可变对象的。
因为不可变类型的数据结构不支持更改。
了解:当多个变量有相同的值时,只是在该值上多了几个计数,所以也叫引用计数。当某个变量的值变成了其他不可变的值,当前值计数减少,新值计数增加。
什么是深浅拷贝
拷贝我们都知道,也就是复制的意思。电脑中的复制会产生多份资源,并且需要多 n 倍的空间保存,当改变其中一个并不会影响到其他的同样的文件,这种我们称为深拷贝。
那哪种是浅拷贝呢?
不知道大家有没有用过快捷方式,电脑里面的快捷方式一般是放到桌面,不过你放到其他地方也一样可以用。
如果你有多个快捷方式,而你又改变了快捷方式所在目录下的文件,所有快捷方式访问到的都会发生改变。也就是说,当你改变其中一个的内容的时候,其他的也会发生改变,这就是浅拷贝(没复制彻底,藕断丝连了)。
浅拷贝
= 相当于给某个变量取了一个别名,本质上还是同一个,所以这里不再说。
浅拷贝需要使用 copy 模块中的 copy 函数实现,比如:
import copy
num = 1
_num = copy.copy(num)
这段代码就是对变量 num 使用浅拷贝创建变量 _num,不过由于要测试深浅拷贝,所以这里不能使用不可变类型。
验证代码如下:
lb = ['不可变', ['可变']]
print("lb 的 id:", id(lb)) # lb 的 id: 2411646709504
print([(l, id(l)) for l in lb]) # [('不可变', 2411638779280), (['可变'], 2411646784640)]
print("------------------------")
# 浅拷贝
lp = copy.copy(lb)
print("lp 的 id:", id(lp)) # lp 的 id: 2411646710144
print([(l, id(l)) for l in lp]) # [('不可变', 2411638779280), (['可变'], 2411646784640)]
print("======================== 改变值 ==========================")
lb[0] = '不可变1'
lb[1][0] = '可变1'
print("lb 的 id:", id(lb)) # lb 的 id: 2411646709504
print([(l, id(l)) for l in lb]) # [('不可变1', 2411646447376), (['可变1'], 2411646784640)]
print("------------------------")
print("lp 的 id:", id(lp)) # lp 的 id: 2411646710144
print([(l, id(l)) for l in lp]) # [('不可变', 2411638779280), (['可变1'], 2411646784640)]
从验证结果可以看到,当修改列表中的不可变元素(“不可变”)时,并不会影响另一个变量。
但是当修改列表中的可变元素(“可变”)时,另一个会跟着一起改变。
这就是所谓的浅拷贝,它并没有将列表中的元素完全拷贝过来,一部分还只是拷贝的 快捷方式。
浅拷贝为什么没拷贝完?
还是上面的例子,下面画一个图方便理解其原理:

-
当创建 lb 的时候,会在内存中开辟一个空间保存列表,其中存在两个元素,一个是字符串,一个是字典
- 字符串是不可变类型,它会保存在一个特殊的区域,当有一个变量使用到它的时候,就会有一条线指向它,每多一条则计数加一。当变量的值改变,则该线会指向其他不可变类型。
- 字典是可变类型,它与不可变类型不同,并不会直接存在于特殊空间,而是还会开辟一个空间来保存可变类型,这个空间里面如果有不可变类型,则会再次指向特殊空间中的元素。
-
图中还有 lf,它是直接把线连接到了 lb 所开辟的空间上,所以任何一个改变,另一个都会改变
-
创建 lp 时,会再次创建一个空间保存,其中元素则与 lb 的一样
-
第一个元素是不可变类型,直接指向特殊空间的元素。
-
第二个元素是可变类型,指向该元素的空间,而该空间和 lb 的第二个元素空间一样,所以任意一个改变该空间中的内容,另一个也会改变。
需要注意的是,修改第二元素和修改第二元素所指向空间内的内容是有区别的。比如极端一点,你把第二个元素该成不可变类型,那自然就不会影响其他的了。
-
深拷贝
同样是上面的例子,只需要将浅拷贝替换成深拷贝就行,而深拷贝的用法如下:
import copy
num = 1
_num = copy.deepcopy(num)
修改后的代码:
lb = ['不可变', ['可变']]
print("lb 的 id:", id(lb)) # lb 的 id: 2411646709504
print([(l, id(l)) for l in lb]) # [('不可变', 2411638779280), (['可变'], 2411646784640)]
print("------------------------")
# 深拷贝
lp = copy.deepcopy(lb)
print("lp 的 id:", id(lp)) # lp 的 id: 2411646710144
print([(l, id(l)) for l in lp]) # [('不可变', 2411638779280), (['可变'], 2411646784640)]
print("======================== 改变值 ==========================")
lb[0] = '不可变1'
lb[1][0] = '可变1'
print("lb 的 id:", id(lb)) # lb 的 id: 2411646709504
print([(l, id(l)) for l in lb]) # [('不可变1', 2411646447376), (['可变1'], 2411646784640)]
print("------------------------")
print("lp 的 id:", id(lp)) # lp 的 id: 2411646710144
print([(l, id(l)) for l in lp]) # [('不可变', 2411638779280), (['可变'], 2411646786767)]
图解

深拷贝和浅拷贝大多数都差不多,主要区别在于深拷贝时,对可变元素也会开辟不同的空间。
两个列表存在于不同的空间中,改变其中一个,另一个也并不会改变。

浙公网安备 33010602011771号