关于python标识符[identifier]、赋值号[assignment operator]、变量[variable object]的一些思考
废话不多说,我们来看几个示例:
1 引用[reference]是什么
a=7
b=a
a="liberty"
print(b)
#output:
#7
结果是7,我们来看代码:
第一行,我们先在内存中创建了一个标识符“a”,再在另一块内存区域中创建了一个int类型的变量对象:7,将标识符“a”指向“1”这个对象所在的内存块
第二行,创建标识符“b”,然后将“b”也指向“7”这个对象所在的内存块
注意,并不是标识符“b”指向了标识符“a”,而是直接指向存储对象[object]数据的内存,所有标识符都是直接指向对象。对象如果属于聚集数据类型[collection date type],父级包含子级关系的体现存在于存储对象[object]数据的内存之间的互指,而不是标识符之间的互指。//如下图
第三行,在存储对象[object]数据的内存区域中创建了一个int类型的变量对象:2,将标识符“a”指向“2”这个对象所在的内存块。标识符“b”指向不变。//如图
第四行,打印“b”所指向int对象,数字 7 的值
引用[reference]是通过赋值号[assignment operator]表示的一种关系,具体为由标识符[identifier]直接指向变量对象[variable object]的内存的关系
不是标识符之间的互指!!!也不是标识符通过标识符间接地指向变量对象!!!
“a”–>“b” 是典型的错误认识!!!
2 深拷贝与浅拷贝
链接的这篇博客写得很好,且有形象的图片说明
链接
3 python解析器对变量作用域原则的实现逻辑
首先介绍变量作用域的LEGB原则:
python在根据python标识符[identifier]查找变量时是要遵循一定的先后顺序的,先从哪找,再从哪找,最后从哪找,这就是LEGB原则。
L是local,指局部变量,作用于函数内部。
E是Enclosing function locals可能是嵌套函数内,比如python嵌套函数。
G是Global,是全局变量,定义在函数体外,在整个文件中都可以访问。
B是Buildin,Python内置模块的名字空间函数名称等,比如dict、len()等。
Python的命名空间实际是一个字典,字典内保存了标识符,也就是变量名与变量对象之间的映射关系,因此,查找变量名就是在命名空间字典中查找键-值对。
LEGB原则很好理解,不过我们这里要讨论的点不在于此
下面这个例子,会让你对LEGB的实现逻辑以及变量的键-值对关系有一个新的理解
示例:test.py
def change():
a=a-1
print(a)
a=1
change()
print(a)
运行结果:
UnboundLocalError: local variable ‘a’ referenced before assignment
这结果有点让人摸不着头脑,怎么?难道函数内的局部环境不能访问全局变量了吗?
其实并不是,我们再来读一下代码,细细地读:
我们先定义了一个函数change,没有问题
然后把int 1 赋给变量名 a ,这是一个全局变量,也没有问题
之后调用change(),问题出现了,就出现在这里
a=a-1
下面说明报错原因:
因为计算机读代码也是一部分一部分从前往后读的,在读到“a
”时,解析器发现这是一个变量,且在局部环境中,也就是函数change()的生命周期中,之前并没有定义这个变量,所以向外查找,发现“a
”是一个全局变量。
但是紧随其后的是“=
”,这意味着解析器以为用户要求它执行的操作,应该是在局部环境中创建一个与全局变量的同名的新变量然后赋值,而不是对全局变量重新赋值。于是解析器新建了一个局部的标识符“a
”,至于它指向什么,待定。相当于字典中键确定值缺省的情况。
最后,解析器读到了“a-1
”,这是一个含变量a
的表达式,现在在整个运行环境中有两个“a
”,或者说有两个对应的“key”(其中一个key的value缺省),按照LEGB原则,解析器当然会优先查找并采用局部的变量“a
”
很不幸的是,这个a
根本就没有指向任何存有对象的内存块,这就产生了无效的引用[reference] ,所以会报错说“referenced before assignment”
代码是给机器读的,在编写者看起来似乎理所当然的地方,机器的理解却会出现莫名其妙的问题。这往往是因为编写者考虑不够全面客观和编写习惯不规范所导致的。
不过,我们只要将代码稍加修改,就会出现不一样的结果
a=[1]
def change():
a[0]=a[0]-1
print(a[0])
change()
print(a[0])
运行结果:
没错,函数从内部直接访问并修改了全局变量a
这里没有报错的关键在于我们将 a=a-1
改成了a[0]=a[0]-1
,赋值号=
左侧并不是一个直接与全局变量同名的变量名,而是一个明确的对全局变量a
其子级对象a[0]
的引用。
解析器知道局部环境中根本就没有创建过变量a
:a[0]
指的一定是已创建的全局变量a
的子级对象a[0]
。解析器也不会额外再创建一个局部的标识符“a
”……
这样一来就,我们就维护了代码语义的唯一性,绕开了歧义引发的问题。
关于变量的定义[assignment]中的LEGB原则:
应当注意的是,对于每一个赋值号[assignment operator],也就是“=”,我们都要当心。
诸如“+=”、“-=”的操作都可能会因为全局-局部重名问题而引发无效引用。
关于变量的引用[reference]中的LEGB原则:
查找变量名就是在命名空间字典中按LEGB原则查找键-值对
解析器很傻,就算键-值的值缺省,它也会尝试着采用……然后报错
4.按对象传参
python中无法人为规定调用函数时传入参数的方式是按值还是按引用
它跟Java一样,采用的是按对象类型的不同,选择不同的传参方式:
参数为不可变类型,按值:
a=1
def change(e):
e=e-1
print(e)
change(a)
print(a)
运行结果:
可以看到,函数运行后,传入的参数全局变量a
没有改变
因为a
是int ,属不可变类型,相当于传入了一个a
的副本
函数运行中对入参的操作只作用于副本,a
本身没有改变
参数为可变类型,按引用:
a=[1]
def change(e):
e[0]=e[0]-1
print(e)
change(a)
print(a)
运行结果:
可以看到,函数运行后,传入的参数全局变量a
改变了
此处的a
是list ,属可变类型,传入的是a
本身
函数运行中对入参的操作直接作用于a
,a
改变了
本文地址:https://blog.csdn.net/Focious/article/details/107357556
下一篇: 荐 Linux基础知识