Python 有点奇怪,因为它将所有内容都保存在字典中,用于各种范围。原始的 a,b,c 在最上面的范围内,因此在最上面的字典中。该函数有自己的字典。当您到达print(a)
andprint(b)
语句时,字典中没有该名称的任何内容,因此 Python 查找列表并在全局字典中找到它们。
现在我们得到c+=1
,当然,它等价于c=c+1
。当 Python 扫描该行时,它会说“啊哈,有一个名为 c 的变量,我会将它放入我的本地范围字典中。” 然后,当它为赋值右侧的 c 寻找 c 的值时,它找到名为 c 的局部变量,该变量还没有值,因此抛出错误。
上面提到的语句global c
只是告诉解析器它使用c
来自全局范围的 ,因此不需要新的。
它说它确实存在问题的原因是因为它在尝试生成代码之前有效地寻找名称,因此从某种意义上说,它认为它还没有真正做到这一点。我认为这是一个可用性错误,但学习不要太认真地对待编译器的消息通常是一个好习惯。
如果有什么安慰的话,在我发现 Guido 写的关于解释一切的字典之前,我可能花了一天时间挖掘和试验同样的问题。
更新,见评论:
它不会两次扫描代码,但会分两个阶段扫描代码,词法分析和解析。
考虑一下这行代码的解析是如何工作的。词法分析器读取源文本并将其分解为词位,即语法的“最小组件”。所以当它击中线时
c+=1
它把它分解成类似的东西
SYMBOL(c) OPERATOR(+=) DIGIT(1)
解析器最终想把它做成解析树并执行它,但由于它是一个赋值,在它执行之前,它会在本地字典中查找名称 c,没有看到它,并将其插入到字典中,标记它未初始化。在完全编译的语言中,它只会进入符号表并等待解析,但由于它没有第二遍的奢侈,词法分析器会做一些额外的工作来让以后的生活更轻松。只有,然后它会看到 OPERATOR,看到规则说“如果你有一个 operator += 左侧必须已经初始化”并说“哎呀!”
这里的重点是它还没有真正开始解析该行。这一切都在为实际解析做准备,所以行计数器还没有前进到下一行。因此,当它发出错误信号时,它仍然认为它在前一行。
正如我所说,你可以说这是一个可用性错误,但它实际上是一个相当普遍的事情。一些编译器对此更诚实,并说“在第 XXX 行或附近出现错误”,但这个没有。