<p>Python有点奇怪,因为它将所有内容都保存在字典中,用于不同的范围。原来的a,b,c在最上面的范围内,所以在最上面的字典里。函数有自己的字典。当到达<code>print(a)</code>和<code>print(b)</code>语句时,字典中没有该名称的内容,因此Python会查找列表并在全局字典中找到它们。</p>
<p>现在我们来讨论<code>c+=1</code>,这当然相当于<code>c=c+1</code>。当Python扫描那一行时,它会说“aha,有一个名为c的变量,我会把它放到我的本地作用域字典中。”然后当它在赋值的右边为c寻找一个c的值时,它会找到它的名为c</em>的<em>本地变量,它还没有值,因此抛出错误。</p>
<p>上面提到的语句<code>global c</code>只是告诉解析器它使用全局作用域中的<code>c</code>,因此不需要新的语句。</p>
<p>它之所以说这行代码有问题,是因为它在尝试生成代码之前有效地查找了名称,所以在某种意义上说它还没有真正执行这行代码。我认为这是一个可用性缺陷,但一般来说,只要学会不要把编译器的消息也当真就好了。</p>
<p>如果有什么安慰的话,我可能花了一天的时间来研究和试验这个问题,直到我发现Guido写了一些关于解释一切的字典的东西。</p>
<h3>更新,请参阅评论:</h3>
<p>它不会扫描代码两次,但它会分两个阶段扫描代码,即词法分析和解析。</p>
<p>考虑一下这一行代码的解析是如何工作的。lexer读取源文本并将其分解为lexems,这是语法的“最小组件”。所以当它触线的时候</p>
<pre><code>c+=1
</code></pre>
<p>它把它分解成</p>
<pre><code>SYMBOL(c) OPERATOR(+=) DIGIT(1)
</code></pre>
<p>解析器最终希望将其生成一个解析树并执行它,但是由于它是一个赋值,在它执行之前,它会在本地字典中查找名称c,看不到它,并将其插入字典中,将其标记为未初始化。在完全编译的语言中,它只需进入symbol表并等待解析,但是由于它不会有第二次传递的奢侈,lexer会做一些额外的工作,以使以后的生活更轻松。只有这样,它才能看到运算符,看到规则说“如果有运算符+=左侧必须已初始化”,并说“哎哟!”</p>
<p>这里的要点是,它还没有真正开始分析行。这一切都是在某种程度上为实际的解析做准备,因此行计数器没有前进到下一行。因此,当它发出错误信号时,它仍然认为它在前一行。</p>
<p>正如我所说,你可以说这是一个可用性缺陷,但实际上这是一个相当普遍的事情。有些编译器对此比较诚实,说“第XXX行或第XXX行附近有错误”,但这一行没有</p>