在学习Python的过程中,我发现Python对变量的处理与C语言有联系又不尽相同,在这里把我的理解做一个梳理总结。本文分三个部分:解释什么指针,Python变量的指针本质,用指针本质解释可变类型与不可变类型。
什么是指针?这里我们用C语言的指针知识简单解释一下。
在 C 语言里,变量存放在内存中,而内存其实就是字节按顺序编号排成的表格,每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位、访问。这里,数据对象是指存储在内存中的一个指定数据,它们都有一个自己的地址,而指针便是保存这个地址的变量。也就是说:指针是一种保存变量地址的变量。
用过快递柜吧?指针说白了就跟快递柜似的:每个格子都有一个编号,我们只需要按照编号去查询,就能找到对应的格子,取出里面的东西。这里的格子就是内存单元,编号就是地址,格子里放的东西就对应存储在内存中的内容。
Python里所有可赋值的东西,即可以出现在"="左边的东西,都是指针。对变量进行赋值的本质,就是让该变量指向某个地方。
#注意看id()返回的变量的地址
a = 1 #a-->内存中某处的1
b = 2 #b-->内存中某处的2
print("a的地址:",id(a))
print("b的地址:",id(b))
print("b-1的地址:",id(b-1))
print("b-1操作之后b的地址:",id(b))
b = a #a和b指向同一个地方,即指向1原来的地方_1
print("赋值之后b的地址",id(b))
上面这段代码运行的时候发生了什么呢?当我们声明a变量的时候,内存中(在我的电脑上是地址为2064345316的地方)创建了一个对象值为1,而a指向这个对象。同理b也指向一个地址是2064345232、值是2的对象。
有意思的来了。我们注意到b-1的地址跟a的地址是一样的(其实无论再声明多少个c,d,e,f… 等于1,你会发现地址还是跟a一样)、b本身还是原来的地址原来的值、b=a之后b也指向了a的地址——其实看到这里,我们就不应把2064345316叫做“a的地址”了,而是称呼它为“对象‘1’的地址”。
也就是说在Python中,内存中对于1只占用了一个地址,而不管有多少个指针(或者叫变量)指向了它,都只有一个地址值 、一个对象。所以说,所有变量本质都是指针,赋值操作只不过是改变了指针指向的位置,并没有改变那块内存中的数据。从这里开始,我们把Python里“变量”和“指针”当成一回事。
(ps:当没有指针指向2时,2064345232这个地址要被“垃圾回收”,也就是对象2不在存在了)
特别的,列表的名字是指向列表的指针,而里面的每个元素也是指针。也就是可以把lst1类比成C语言里指向指针数组的指针。如图:
现在我们可以来看看什么是不可变、什么是可变。
不可变类型:不能修改指针指向的存储在内存中的值,想给此变量赋新值时其实是指针指向了另外的空间。
可变类型:可以改变指针指向的内存中的数据,当该变量的值发生了改变,它对应的内存地址不发生改变。(有没有注意到,可变类型的list和dict都可以看作指向指针数组的指针;而tuple其实也是指向指针数组的指针,但不让改变其中的元素,所有它就变成不可变类型了)
#a is b a,b指向同一个地方时为ture
#a == b a,b指向的地方放的东西相同但不一定指向同一个地方
lst1 = [1,2,3]
lst2 = [1,2,3]
print(lst1 == lst2) #>>True
print(lst1 is lst2) #>>False
lst3 = lst1 # lst3和lst1是同一个东西
lst3[1] = 0
print(lst1) #>>[1, 0, 3]
思考上面这段代码,我们注意到 is和==是不一样的。对于不可变类型不需要区分两者,但是对于可变类型,我们必须时刻留心两个对象是is的关系还是==的关系。
比如,两个string变量str1和str2,str1=str2操作之后,对其中任何一个进行操作都不会影响到另一个。但是两个list变量lst1 = lst2,修改lst2就是在修改lst1,忽略这一点可能会使程序出错。
可变类型与不可变类型的区别使在他们作为函数参数时表现也不同。当实际参数为不可变对象时,进行的是值传递,形参仅仅是实参的复制品,对形参操作并不能改变实参;当实际参数为可变对象时,进行的是引用(理解成指针)传递,形参就是实参。
来看看这段代码:
def fun1(a):
a+1
a = 1
fun1(a)
print(a) #>>1 没改变a
def fun2(a):
a[0]=999
lst1 = [1,2,3]
fun2(lst1)
print(lst1) #>>[999,2,3] 改变lst1了
------------------------我是分割线-------------------------
最后:Python作为一种弱类型语言,其变量内存管理不如C语言那样清晰与规范,并且它本身也没想让用户去过度关注这一点。学习Python更多的关注是如何利用它便捷的实现功能,但是适当的了解一些内存里发生了什么还是有助于我们更好的使用这个语言的。对于变量,能够认识到其指针本质、区分开什么是变量值的变化、什么是变量指向的对象地址的变化就足够了。
如有错误,敬请指正!