联想说 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)]

从验证结果可以看到,当修改列表中的不可变元素(“不可变”)时,并不会影响另一个变量。

但是当修改列表中的可变元素(“可变”)时,另一个会跟着一起改变。

这就是所谓的浅拷贝,它并没有将列表中的元素完全拷贝过来,一部分还只是拷贝的 快捷方式

浅拷贝为什么没拷贝完?

还是上面的例子,下面画一个图方便理解其原理:

  1. 当创建 lb 的时候,会在内存中开辟一个空间保存列表,其中存在两个元素,一个是字符串,一个是字典

    • 字符串是不可变类型,它会保存在一个特殊的区域,当有一个变量使用到它的时候,就会有一条线指向它,每多一条则计数加一。当变量的值改变,则该线会指向其他不可变类型。
    • 字典是可变类型,它与不可变类型不同,并不会直接存在于特殊空间,而是还会开辟一个空间来保存可变类型,这个空间里面如果有不可变类型,则会再次指向特殊空间中的元素。
  2. 图中还有 lf,它是直接把线连接到了 lb 所开辟的空间上,所以任何一个改变,另一个都会改变

  3. 创建 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)]

图解

深拷贝和浅拷贝大多数都差不多,主要区别在于深拷贝时,对可变元素也会开辟不同的空间。

两个列表存在于不同的空间中,改变其中一个,另一个也并不会改变。

posted @ 2023-10-11 19:23  笔锋微凉~~  阅读(7)  评论(0)    收藏  举报