1.函数传入的参数,明明是局部变量,函数内改变,函数外有时却也改变了?
2.将一个变量的值赋值给另外一个变量,后者变化时有时前者也变了?
3.对序列使用乘法将其中元素变成多倍,但有时改变一个元素其它元素也跟着变化了?
4.使用copy()复制一个副本,有时候副本变化后原变量也跟着变了?
等等,诸如此类的问题,作为一个python新手,像我一样你可能会遇到这样类似的问题。每次遇到这样的问题就去查找一下,然后死记硬背函数的哪些参数会变,哪些又不会变,一段时间后记不清了又弄不懂了。接下来我将教你了解python的变量数据储存逻辑,对这些全部情况都可判断到底怎么变化。
一.理论
1.1.变量是一个指向数据的指针
在python中你可以发现,我们使用变量的时候不需要提前定义数据类型,而且不同数据类型的变量还可以随意赋值。因为python的变量其实都是一个指针,在python中可以指向任何一个具体的数据。所以变量不需要提前定义,还可以随意赋值。(可能有些同学觉得 a = int()就是定义a是一个整型变量,其实也只是给a赋值指向了一个整型的数据,所有类似的数据类型都是有一个初始值,如这里a = 0,如果是列表那就是指向一个空的列表)
1.2.驻留机制
由于很多数据会被频繁反复的定义赋值,所以python使用驻留机制存储大部分数据,简单来说就是你对两个变量赋了相同的值,那么这两个指针指向的是同一个内存地址(数据)
下面举一个例子:
可以看到对a和b赋值1,a和b的1是同一个1,但赋值-6,却不是同一个。也就是驻留机制只在一定范围内有效。 如对于整型来说在-5到正无穷生效,这里不对范围做详细介绍,不是本文重点。
重点:对于驻留机制存储的数据,进行运算时,不是直接改变变量指向的值,而是会运算得到一个新的值存储的一个新的地址,所以两个变量都指向1,当其中一个变量值运算改变后,另外一个不会因为驻留机制而跟着改变,例:
这是理解不可变变量的关键!
1.3.序列的存储逻辑
序列类型的变量和其它变量并没有区别,就是一个变量,作为一个指针指向了一个序列对象(如列表list),但是序列内的元素,每一个元素都是一个变量,指针指向一个对象,就是这个原因使得序列内的元素可以是不同的各种数据,图示如下(以列表为例):
a = [1, 0.1, 'a', True]
这是理解可变变量的核心!
1.4.可变变量与不可变变量
不可变变量:包括数字、布尔值、字符串、元组。
对于数字和布尔值,都是指向一个确切的数据,对这个数据进行运算不会改变原值,而是运算得到一个新的地址的新值,然后变量指向这个新的地址,并不会改变原地址内的值。故原来的这个数据是没有改变的,不能改变的,运算是得到了一个新值;(如图中的1没有变,而是多了一个2)
由于前后地址变化了,可以看出不是1变成了2,而是产生一个新的2。
对于字符串和元组,虽然是序列,但是序列中的元素不允许改变,对其进行操作也是得到一个新的序列,然后变量指向新序列。故我们可以看到原来的字符串并没有改变,而是生成了一个新的字符串,但新的字符串中的'c'和变量b的'c'是同一个'c'(地址相同)
再附上一份截图证明运算后的变量a和运算前的a不是同一个地址,而是指向一个新字符串。对a中的字符进行赋值会报错,也就是不能改变值
总结:不可变变量赋值后,值是不能运算改变的,运算只会产生一个新的值,然后变量指针指向这个新值
可变变量:列表和字典,可变变量的值是可以改变的,改变变量的值是在原值基础上改变,不会生成一个新的列表或者字典。
可以看到对列表中的元素进行运算之后改变这个运算的指针指向的对象,不会创建一个新列表,a还是指向原列表,对列表加新的元素也是。
通过以上内容便足够进行分析 ,主要的分析是搞清楚这些操作的变量,地址是怎么分配的。知道了变量的指向,再根据可变变量与不可变变量运算的规则进行分析。
二.实战
2.1.函数参数
函数传入的参数a,会生成一个局部变量b,但是这个变量b指向的值(地址)和传入的参数a是一样的。所以对于传入的不可变变量,不可变变量的值是无法改变的,进行运算会产生新的值,这个局部变量b指向新的值,传入参数的原变量a指向的值不会变。但如果传入参数未可变变量,由于a,b指向的是同一个值,进行运算如果改变了值中的元素,由于a的值和b是同一个,故a的值也改变了。下面上例子:
运行结果:
图解:
总结:可以看出传入参数为不可变变量时,函数外不受影响,为可变变量时,函数外会跟着变化
2.2.变量赋值变量
将一个变量a赋值给b,即b=a,会使得a,b是同一个值(地址相同),这时候对其中一个变量进行运算,如果是不可变变量,运算的结果是一个新值(有新的地址),另外一个变量地址不变,自然不受影响;如果是可变变量,由于a,b指向的是同一个可变变量,通过其中一个运算改变了改可变变量中的值,另外一个自然也会改变输出,例子如下
图解:
注意a = b,则两者指向同一个值,但对于列表,a = [] + b ,指向一个新的值,只是这个值等于空加b的值也就是b的值,但不是同一个值(数值相同,地址不同)。所以a = a + c的值和原a不是同一个(地址不同),但a += c的地址和原a是同一个。两者虽然值相同,但前者是a指向一个新列表,列表的值等于a+c,后者是在a的值中加入c的值。
2.3.序列乘法
使用序列的乘法运算可以将序列的元素快速倍化,但是倍化的元素指向是相同的(地址相同),故如果序列元素是可变变量,则会出现,对其中一个可变元素进行运算,其它倍化的得到的可变变量运算也会改变。
这里理解与前面差不多,不作图解了。
2.4.copy()与deepcopy()
赋值语句(=)的新变量值和旧变量值相同,也是是同一个值(数值相同,地址相同)
copy()得到的新变量,如果是可变变量,则值相同,但不是同一个值(数值相同,地址不同),但其中的元素可原变量元素是同一个值(数值地址都相同)
deepcopy()得到的新变量,只要是可变变量,包括在元素中的可变变量,在元素中继续嵌套的可变变量都是新地址(数值相同,地址不同)
两种copy对于不可变变量不会创建新地址。
例子如下:
图解:
三.后言
这是作者(惜豫)我第一次认真写的一篇教学博客,带有个人经验成分,可能会有一些问题有什么问题欢迎大家在评论区指正。其实作者我就是遇到了上面说的这些问题,一个变量变了,其它变量有时候跟着变,有时候没变,每次都要学习别人的博客一遍,慢慢发现其实这些问题本质上是一样的,所以写了这篇博客,希望能达到一个通解的作用。希望这篇文章能对大家有所帮助!