欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

关于python标识符[identifier]、赋值号[assignment operator]、变量[variable object]的一些思考

程序员文章站 2022-07-10 21:30:23
废话不多说,我们来看几个示例:1引用[reference]是什么a=7b=aa="liberty"print(b)#output:#7结果是7,我们来看代码:第一行,我们先在内存中创建了一个标识符“a”,再在另一块内存区域中创建了一个int类型的变量对象:7,将标识符“a”指向“1”这个对象所在的内存块第二行,创建标识符“b”,然后将“b”也指向“7”这个对象所在的内存块注意,并不是标识符“b”指向了标识符“a”,而是直接指向存储对象[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]数据的内存之间的互指,而不是标识符之间的互指。//如下图
关于python标识符[identifier]、赋值号[assignment operator]、变量[variable object]的一些思考

第三行,在存储对象[object]数据的内存区域中创建了一个int类型的变量对象:2,将标识符“a”指向“2”这个对象所在的内存块。标识符“b”指向不变。//如图
关于python标识符[identifier]、赋值号[assignment operator]、变量[variable object]的一些思考
第四行,打印“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)

运行结果:
关于python标识符[identifier]、赋值号[assignment operator]、变量[variable object]的一些思考
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])

运行结果:
关于python标识符[identifier]、赋值号[assignment operator]、变量[variable object]的一些思考
没错,函数从内部直接访问并修改了全局变量a
这里没有报错的关键在于我们将 a=a-1改成了a[0]=a[0]-1,赋值号=左侧并不是一个直接与全局变量同名的变量名,而是一个明确的对全局变量a其子级对象a[0]的引用。
解析器知道局部环境中根本就没有创建过变量aa[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)

运行结果:
关于python标识符[identifier]、赋值号[assignment operator]、变量[variable object]的一些思考
可以看到,函数运行后,传入的参数全局变量a没有改变
因为a是int ,属不可变类型,相当于传入了一个a的副本
函数运行中对入参的操作只作用于副本,a本身没有改变

参数为可变类型,按引用:

a=[1]

def change(e):
    e[0]=e[0]-1
    print(e)
    
change(a)
print(a)

运行结果:
关于python标识符[identifier]、赋值号[assignment operator]、变量[variable object]的一些思考
可以看到,函数运行后,传入的参数全局变量a改变了
此处的a是list ,属可变类型,传入的是a本身
函数运行中对入参的操作直接作用于aa改变了

本文地址:https://blog.csdn.net/Focious/article/details/107357556