关于Python中的可变类型、不可变类型、深浅拷贝以及小整数池与驻留机制的总结

关于Python中的可变类型、不可变类型、深浅拷贝以及小整数池与驻留机制的总结

0.前言

对于从c/c++转到Python的人来说,可能会有很多的不适应,除格式上的巨大区别外,如变量的地址、弱类型、各种推导式等。而对于一个学习过c++的人来说,赋值常常是指传,但在Python中,很多情况都是传引用,我们以下列代码引出今天的问题(来源于知乎)

a = {"num":1}
list1 = []
for i in range(10):
    a["num"] = i
    list1.append(a)
print(list1)

结果如下

[{'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}]

而当代码改成下列时

a = {"num":0}
list1 = []
for i in range(10):
    a = {"num":i}
    list1.append(a)
print(list1)

结果就会变成

[{'num': 0}, {'num': 1}, {'num': 2}, {'num': 3}, {'num': 4}, {'num': 5}, {'num': 6}, {'num': 7}, {'num': 8}, {'num': 9}]

对于从c/c++转过来的人,通常认为第一个代码也应该输出第二种结果,如同c中的数组一样,但这是由于Python的赋值机制以及序列的存储机制造成的,下面我们一一来解析

1.可变类型与不可变类型

Python的数据类型可分为两种,可变类型和不可变类型

  • 不可变类型:int整型、float浮点型、string字符串型、tuple元组
  • 可变类型:set集合、list列表、dict字典

不可变类型

不可变类型是指变量若被修改,其地址也会发生变化;即地址上的内容是不可变的,不可变类型的修改是通过修改地址来完成的,即创建一个新的对象,再让变量指向这个新的对象

如下列代码

a = 1
print(id(a))
a += 1
print(id(a))
4317505856
4317505888
a = '1'
print(id(a))
a += '1'
print(id(a))
4389343896
4370932912

元组无法修改,所以不做展示

可变类型

可变类型是指在修改变量时其地址不会发生变化

值得注意的是,重新赋值这一操作并非修改,而是创建一个新的变量,所以地址也会发生变化,这点不在我们的讨论范围之内

set1 = {1,2,3,"123"}
print(id(set1))
set1.add(1)
print(id(set1))
4334929728
4334929728
list1 = [1,2,3]
print(id(list1))
list1.append(1)
print(id(list1))
4373599680
4373599680
dict1 = {1:1,2:2}
print(id(dict1))
dict1[1] = 2
print(id(dict1))
4308701696
4308701696

2.深浅拷贝

在数据传递的过程中,可能会发生数据被修改的情况,为了防止数据被修改,通常需要传递一个副本,这样,副本的修改不会影响到原数据,为了生成这个副本,就产生了拷贝

赋值运算

list1 = [1,2,3]
list2 = list1
list2.append(4)
print(list1,list2)
[1,2,3,4]
[1,2,3,4]

其中,赋值语句是一个指向,即传引用,与拷贝无关

我们发现,这样的赋值会使得修改互相影响,通常这是不安全的

浅拷贝

浅拷贝仅仅复制对象的各种数据,不会直接指向复制对象,可使用.copy()方法或者是copy.copy()方法来实现

list1 = [1,2,3]
list2 = list1.copy()
list1.append(1)

此时list1被修改,但是list2不会被修改

但是由于列表中只是存储地址,有些情况下,修改不可变序列也会影响到浅拷贝的对象

list1 [1,2,3,[1]]
list2 = list1.copy()
list1.[-1].append(1)
print(list2) #[1,2,3,[1,1]]

在浅拷贝中,列表的拷贝也仅仅是创建一个新空间,将原本列表中排列着的地址存入,其中存储的可变数据地址仍然共用

深拷贝

对于上述问题,我们可以引入深拷贝来解决

import copy
list1 [1,2,3,[1]]
list2 = copy.deepcopy(list1)
list1.[-1].append(1)
print(list2) #[1,2,3,[1]]

深拷贝可将拷贝的可变序列也复制一份,放入新的地址中,不可变序列则沿用之前的

由于浅拷贝效率更高,除非特殊需要,否则不用深拷贝

小整数池以及驻留机制

在Python中,不同变量的地址不同,即使是重复声明相同的变量

但是当两个不可变类型的变量声明在同一行时,为了优化,其地址也相同

而在如今许多编译器中,通过优化,不可变类型的变量即使不在一行声明,其地址也会相同,所以以下测试都在终端命令行中进行

小整数池机制

为了效率,Python中小整数仅仅会在内存中存放一份,所以其地址是一样的,其中小整数的范围是 [ − 5 , 255 ] [-5,255] [5,255],这就是Python的小整数池机制

在这里插入图片描述

只有整数存在这种情况,小数则不存在

字符串驻留机制

同小整数池机制一样,字符串也有类似的机制。对于仅仅由数组、下划线、字母组成的字符串,内存也会只保存一份

但注意的是,驻留时机是在编译的时候驻留的,不是在运行的时候驻留的

在这里插入图片描述

在编译时期,c的值不能被确定,只有在运行时才能被确定,所以不会驻留

所以在Python中对字符串的改动会采用重新创建新对象的办法,且原字符串会被驻留,因此不推荐使用+来拼接字符串,推荐使用join函数

join一次性计算好最终需要的内存空间,再把字符串存入

+则是每次把左右两个字符串都复制到一个新空间

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值