几行代码帮助快速理解Python中可变数据类型与不可变数据类型

先来看一段简短的代码,对比其中的三小块输出结果:

d = {} # d是个字典 

a = 2 
d[1] = a
a = 4    
print(d[1]) ## 输出结果为 2 

b = [1, 2] 
d[2] = b 
b[0] = 3
print(d[2]) ## 输出结果为 [3, 2]

c = [1, 2]
d[3] = c
c = [3, 4]
print(d[3]) ## 输出结果为 [1, 2]

博主是自学c/ c++出身的,后因学习过程中实际问题需要直接上手了Python,当时就觉得上手快、简单、好用(当时甚至开玩笑觉得这玩意用多了降智hhh),所有也就没对Python进行稍基础的系统性学习。今天在调试程序时,发现了类似于以上代码的问题,输出结果完全不符合自己的预期,懵了。仔细一查,原来发现Python中有“可变数据类型”与“不可变数据类型”一说,百度了一圈感觉都是在简单地介绍不可变的数据类型有 Number(如int型、float型等)、String、Turple,可变的有List、Dict、 Set、User-defined Classes。其他有涉及怎么可变、咋不可变的也感觉说的不清不楚。自己捣鼓了半天,翻了好多资料和博文,发现以上代码的所涉及的问题其实还与Python的“赋值”操作有关,而Python的“赋值”与C++中的“赋值”其实大相径庭。经博主本人的研究(实际上就是不断地踩坑、填坑的过程),总结自己学习与思考历程,现整理出以上个人觉得比较简洁但比较典型的代码并进行对比解释,希望通过从这自己这一角度的思考对比,能够帮助像我一样的Python初学者有效地理解Python中的赋值与传参、可变数据类型与不可变数据类型这些概念。

在逐行分析以上代码前,我们先理解Python的赋值语句:区别于c++,Python实际上不存在“赋值”这一操作,Python的“变量”更像是个“标签”而已。以如下这一行语句为例,

a = 2 

 这不能简单地就理解为将2赋值给a这个变量,a的值为2。而Python在执行这行语句时实际上干的事是:在内存中找一块空地,在这块空地上创建一个值为2的int型对象,然后给这个对象贴了一个标签 a。不用大白话,就是其他博文里说的:Python新建了一个int型对象2, a 则是值为2的这一int型对象的引用。简单来说,就是拿a指向了新建的这一int型对象2 (为便于理解,下文中均以此说辞代替“a是2的引用”这类说法)。

那对于下面这一行呢?

b = a

同上一段,也不能简单理解为将a的值赋给b。这里应理解为:将b指向a所指的对象(亦即 让b和a引用同一个对象、b和a是同一个对象的引用)。

稍微总结一下,就是Python的“变量”更像是一个仅仅是符号、记号、标签类的东西,重要的是其所指的、处于内存中的对象,也就是说我们要关注它所引用的对象本身。

好,有了这一概念,我们来逐行分析开头那段代码(现在每一行代码后都有相应注释,说明程序在执行这一句时干了什么)。

d = {} # d是个字典 

a = 2 # Python在内存中找了块空地,在这块空地上新建了一个值为2的int型对象,让a指向它
d[1] = a # 在d这个字典中新建立了一个键值对,key是int型对象、其值为1,指向的value是 a所指的int型对象2
         # 这里不能将d[1]理解为指向的是a, 前文已经说了a仅仅是个记号,重要的是a所指的对象2
a = 4    # Python又新建了个值为4的int型对象,a指向这个4,而不再指向2
print(d[1]) # 输出结果为2
            # 因为d[1]所指的对象仍是a之前所指的2。尽管a在上一句中新指向4,但2那个对象还在,只不过是a不指向2了
            # 那这里和不可变数据类型有什么关系呢? Int是不可变数据类型,在内存中的一块空地上创建值为2的这个对象后,
            # 只要这块空地没有被回收,那么这块空地上的对象永远都是2,这块空地上的值是不可变的
            # 故d这个字典中 值为1的这个key 仍指向对象2


b = [1, 2] # Python在内存中另找了块空地并新建了一个list对象[1, 2],让b指向它
d[2] = b # 在d这个字典中新建立了一个键值对,key是int型对象、其值为2,指向的value是 b所指的list型对象[1, 2]
b[0] = 3 # Python先新建一个int型对象3,要 将b所指的对象的第一个元素 指向 3
         # 而b所指的对象是个list, 值为[1, 2]
         # list是可变数据类型,对可变类型的对象本身操作的时候(注意:是对可变类型对象本身操作),
         #   不需要再在其他地方申请内存,只需要在原对象直接修改或者此对象后面连续申请(+/-)即可,
         #   也就是它的内存地址会保持不变,但区域会变长或者变短(即这块空地的位置不变,但空地上可涂改、空地也可变大变小)        
         # 那么,既然要[1, 2]的第一个元素指向3,那就索性就直接改指向3好了,反正自身是可变的
         # 那么b所指的这个list对象就原地变成了[3, 2]
         # 而d这个字典中 值为2的这个key 指的还是这个list对象 (因为这个list对象的内存地址没有发生改变,还是那块空地)
print(d[2]) #只不过这个list的值变成了[3, 2],所以这里输出的结果为 [3, 2]



c = [1, 2] # Python在内存中又另找了块空地,并在上面新建了一个list对象 [1,2],让c指向它
d[3] = c   # 在d这个字典中新建立了一个键值对,key是int型对象、其值为3,指向的value是 c所指的list型对象[1, 2]
c = [3, 4] # 注意,这里和上一段不同(上一段是存在对List对象本身的修改),这里仅仅是让c换了个指
           # 这一赋值语句是Python是又新建了个list对象[3, 4],让c指向了这个[3, 4],毕竟c只是个记号,让它指谁就得指谁
print(d[3]) # d[3]指的还是放[1, 2]的那块空地,所以这里输出的还是[1, 2]
            # 同第一段不同的是,因为[1, 2]是个可变的list,在这块空地被系统回收之前,其值是可变的,如下三行代码
c = d[3] #让c指向d[3]所指的对象,即让c又指回[1, 2]这个对象
c.append(5)
print(d[3]) # 输出结果是[1, 2, 5]

以上代码注释看懂了吗?那我们加一个函数传参,根据以上代码为例,来举一反三。

def changelist(q):
    q = [6, 6, 6]
a = [7, 8, 9]
changelist(a) # 这一句,首先函数传参,因为Python中无“赋值”,
              # 传参实际上就是是让q和a去指向同一对象[7,7,7], 即传参使得q和a都是[7, 7, 7]这一列表的引用;
              # 传参完后执行函数体,即 q = [6,6,6], 则Python新建了[6, 6, 6]这一对象让q新指向它
              # 而a扔指向[7,7,7],所以下一句print(a)的输出结果仍为[7,7,7],而下段例子则不同
print(a) ## The result is [7, 8, 9]


def changelist2(q):
    q[0] = 1
b = [7, 8, 9]
changelist2(b)
print(b) ## The result is [1, 8, 9]


def changelist3(q):
    q[0] = [1, 2, 3]
c = [7, 8, 9]
changelist3(c)
print(c) ## The result is [[1, 2, 3], 8, 9]


def changenum(p):
    p = 5
m = 1
changenum(m)
print(m)  ## The result is 1

怎么样,自己的预期和输出结果对上了吗?实际上上文所说的 a指向一个对象xxx,只是为了方便理解所采取的比较通俗的说法,这里应该被正式地、规范地理解为 a是对象xxx的一个引用。

此外Python中变量的作用域等等也都和C风格中的不一样,不过这个问题网上一搜都解释得很清楚 ~ 这篇文章旨在从细节上快速厘清Python中“赋值”、“可变数据类型”与“不可变数据类型”这些概念,希望能够对像我一样的Python初学者有所帮助。

参考: 

https://www.cnblogs.com/jiangzhaowei/p/5740913.html

https://www.cnblogs.com/sea-stream/p/11188732.html

https://blog.csdn.net/dan15188387481/article/details/49864613

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值