题目描述
函数内全局变量引用的理解
题目来源及自己的思路
自学中发现的理解问题
相关代码
// 请把代码文本粘贴到下方(请勿用图片代替代码)
c = 1
def test():
c= c+1
print(c)
test()
错误:输出c未被初始化
c = 1
def test():
a= c+1
print(a)
test()
输出:2
个人理解不知道正确否
python赋值,先进行=右边的计算,进行了c的引用,再进行了c的赋值,就报错了,变量必须先赋值在引用。
和全局挂钩理解是不是:
一般情况下:函数内部可以调用全局的引用,但不能对其赋值, 除非global,nonlocal或者参数传入
理解的都没问题。但我想聊聊出现这种情况的原因。在讲原因之前,需要先知道python中变量的搜索顺序,这个顺序是
LGB
(不考虑闭包情况)即local本地,global全局,builtin内建。比如:函数内声明了局部变量
a
,在打印中使用,在本地环境中命中,因此使用的是 3。也许你会问这个知识点我早就知道了,这和本问题有什么关系呢?
有的,难道你不觉得奇怪吗?报错是变量未初始化,而不是变量未定义。
题目中函数内
c= c+1
就已经表明了声明的变量 c 是属于局部变量的。 按理说,先执行赋值语句右侧,而此时 c 并没有声明,应该在全局环境命中才对啊。所以想象中的结果应该是局部变量c = 2
而全局变量的 c 保持原值。但是,这只都是想当然。讲了这么多其实我是想引出,python虽然是动态语句,但它还是会对代码做扫描工作的,会有收集有用的静态信息。函数的应该信息会放在
code
对象中,里面的信息就包含了局部变量名称的集合,可以通过co_varnames
得到,如下:因此,函数test在执行前,变量 c 就已经被声明在局部变量环境中了,而不是我们自认为的当赋值语句运行后才会在局部变量里。于是,这就导致了报错信息是变量未初始化而不是变量未定义。
====== 分割线 =========
题主评论要求:
这三个意思基本差不多,没必要分得太开。把变量环境理解成一个字典
name_env = dict()
其实就很好理解了(事实上python底层也确实是这样处理的)。对于在这个环境内要创建一个名为a
的变量,就可以是name_env['a'] = value
的形式了。这个过程也就是赋值语句形如
a = value
时会调用赋值的指令STORE_NAME
。我们看一下这个赋值过程你就理解了。这部分代码在
ceval.c
中,详见 ceval.c代码不多,可以逐个分析下,第一行获得的
name
就是赋值语句a = value
的 a,a以python类型str
形式存在。第二行v
从栈中获取,也就是value
的值。第三行ns
是从帧对象中获得局部变量环境(大多数情况下是个字典类型,如果帧环境不在函数或类中,取得的是全局变量环境)。PyDict_SetItem(ns, name, v);
和PyObject_SetItem(ns, name, v);
就可以理解为ns[name] = v
和setattr(ns, name, v)
了,创建过程就是这样了。变量究竟是创建还是初始化还是覆盖已有的变量值,其实底层并不关心。
回到本地中,本地变量的符号表会保存在静态信息里面,我猜测搜索变量时有优先去静态信息中得到信息,来更快的知道变量应该是在局部还是全局中查找吧。