作为程序员的基本素养,你了解Python变量引用吗

在我们编程当中,变量是最最基础的概念,它的重要就相当于我们盖大楼用的一块砖一样,是不可或缺的。所以,理解变量的运行方式是至关重要的。

九层之台,始于垒土;合抱之木,始于毫末;千里之行,始于足下!

今天就让我们一起来谈一谈Python变量的那些事。

1. 变量不是盒子

让我们看看下面的代码

1. a = "hello,world"
2. b = a
3. c = [1,2,3]
复制代码

对于我们初学者来说,变量的赋值是最容易走进误区的地方。最常见的误区是什么呢?

定义一个变量,就在内存中创建一个变量盒子,然后把变量的值放在这个盒子中

让我们看看下面这张图,这种想法是大错特错的。就是因为这种误区,使得我们的代码可能遇到很多问题。

image.png

那正确的是什么?变量赋值的时候做了什么呢?

2. 千奇百怪的变量

1. a = "hello,world
2. b = a
3. c = [1,2,3]
复制代码

所以上面的代码究竟做了什么?在这之前,我先大家讲个故事。

上个世纪90年代的时候,我们大名鼎鼎的蟒蛇自助大酒楼开业了。顾名思义,这家酒楼主要是做自助餐的,但是呢?它每个顾客只能吃一种美食,并且会把美食分布在不同的房间里面,美食种类是不固定的,顾客有什么需求就提供什么。
这天,我们的小a同学来到了酒楼,跟前台说,我要吃“hello,world”,于是,酒店前台就新开了一间房间,房号为:00010,并且在里面放上了我们的“hello,world”,并且给了 a 一张通行证,这张通行证只能通往00010号房间。并且记录下 hello,world:食用人数:1,当a想吃的时候,就自己拿通行证去00010号房间去拿
过不久,小b也来到了我们的酒店,跟酒店前台说,我要跟a吃一样的东西。于是酒店前台,也给了b一张通行证,b根据通行证也能去到00010号房间去拿hello,world。酒店前台再次记录: hello,world:食用人数:2
紧接着,我们小c同学也来了,他跟b不一样,他有自己想吃的食物。他跟酒店前台说:我要吃[1,2,3]。顾客有了新的需求,酒店前台就又新开一件房间,房号为:00020,并且也在里面放上了[1,2,3]。同样给c一张通行证。然后记录下: [1,2,3]:食用人数:1

我们可以带着这个故事往下面看

image.png

根据这张图,我们上面的故事中:

顾客a、b、c:变量a、b、c\

酒店:内存空间
酒店前台:Python解释器
房间:为对象划分的内存空间
房间号:对象所在的内存地址
食物:各种各样的对象(字符串、列表、字典、数字。。。)
前台记录的食用人数:引用计数
通信证号码:变量引用的内存地址
实际上当我们对一个变量赋值的时候,我们的变量并没有存储这个值。而是绑定了一个内存地址id,当我们要用这个变量的值的时候,就去内存中寻找这个地址的存储的值

接着上面的故事,我们的小a同学,吃腻了hello,world,现在想吃123456,于是跑去跟酒店前台说,我现在想吃123456了,酒店前台二话不说,新开了一间房间,房号为00030,里面放着123456,并且更新了a同学的通行证,此时这张通信证只能去00030号房吃123456。前台继续记录 hello,world:食用人数:1、123456:食用人数:1

在代码中,我们改变了a变量的值,会发生什么呢?

我们再看看,改变a的变量会发生什么?

a = 123456

会这样吗?

image.png

我们改变a的值的时候,并不会直接去改变a指向的内存地址存储的值,而是新开辟一个空间存放新的值123456,把a的指向改成新空间的地址00030,如下图所示。正确的应该是这样:

image.png

我们的b同学非常喜欢模仿,她现在又不想吃hello,world了。于是他就跑下楼去跟酒店前台说:我要吃[1,2,3]。注意哦,这次b同学说的是,我要吃[1,2,3],而不是说我要吃跟c一样的。虽然他们的食物是一样的。但是我们的前台并不会直接给b 00020号房间的通行证。而是新开一间房间,房间号00040,里面放[1,2,3],并且给b一张通行证指向00040号房间。同事记录上(00040)[1,2,3]:食用人数:1、(00010)hello,world:食用人数:0

b = [1,2,3]

为什么呢?其实这里很好理解,因为我们b赋值的时候是新建了一个对象。只要新建对象,就会重新开辟空间。

但是,像这样

b = c

这样并没有新建对象,而是将c的引用传递给了b,他们都指向一个对象。这里小伙伴们留意一下,不要被我的例子给带跑偏了。

这个时候,我们的酒店前台发现00010号房间里面的hello,world已经没人吃了。这个时候酒店前台就会把这间房间收回来,并且把里面的hello,world食物丢掉。这个就是python的垃圾回收机制。

image.png

此时,又来了以为同学d,d跟酒店前台说,我要跟c吃一样的,酒店前台就给d发了一张通行证,d根据通行证能去到00020号房间去拿[1,2,3]。酒店前台再次记录: [1,2,3]:食用人数:2

d = c

image.png

但是我们的d同学非常挑剔,他不满足现有的[1,2,3],他想要加点菜,也是就跟前台说要加菜:这时候00020房间里面放的东西就变成了[1,2,3,4]

d.append(4)

image.png

此时,我们发现了一个问题,c同学什么也没有干,但是他能吃的食物从[1,2,3]变成了[1,2,3,4]

这究竟是什么问题呢?

为什么我们之前a从“hello,world”变成123456的时候,是新开辟一块空间。但是现在d从[1,2,3]变成[1,2,3,4],却直接在原内存空间里修改呢?

这就是python经典的面试题:对象的可变性?什么是可变对象,什么是不可变对象?

3. 可变对象与不可变对象

在python中,一切皆对象,但是这对象也分为两类:

可变对象(3个):List(列表)、Dictionary(字典)、Set(集合)
不可变对象(3个):Number(数字)、String(字符串)、Tuple(元组)
Python中看可变与不可变数据类型,主要是看变量所指向的内存地址处的值是否会改变 。

3.2 不可变对象

>>> a = 10000 
>>> id(a) 
139964684838128 
>>> a = 30000 # 不可变对象,改变变量的值,实际上是实例化新对象、开辟新内存空间 
>>> id(a) # 产生了新的内存地址,说明已经不是原来的对象了 139964684837872 
>>>
复制代码

3.3 可变对象

>>> a = [1,2,3]
>>> b = a
>>> id(a)
139711046464264
>>> id(b)
139711046464264
>>> b.append(4)  # 可变对象,允许在原地改变对象的值
>>> id(b)
139711046464264  # 内存地址没有改变,说明是在原内存空间改变值
>>> id(a)
139711046464264
>>> b
[1, 2, 3, 4]
>>> a
[1, 2, 3, 4]
复制代码

总结:

可变对象:变量所指向的内存地址处的值是可以被改变的
不可变对象:变量所指向的内存地址处的值是不可以被改变。