<p><a href="https://stackoverflow.com/a/45579117/1405065">other answer</a>对于解释出了什么问题非常有用。我添加了我自己的答案,试图解释问题背后的一些原因(即“为什么”而不是“什么”)。你知道吗</p>
<p>首先,您需要稍微了解一下Python的体系结构。我们经常将Python描述为一种“解释”语言,而不是像C这样的“编译”语言,但这并不是全部。虽然Python不直接编译为机器代码,但是当程序运行时,解释器不会在原始源代码上运行。相反,有一个中间步骤,源代码被编译成字节码。编译是在加载模块时自动进行的,因此您甚至可能不知道它(尽管您可能已经看到了编译器为缓存字节码而编写的<code>.pyc</code>文件)。你知道吗</p>
<p>无论如何,回到您的作用域问题:Python的编译器使用字节码指令来告诉解释器访问局部变量,而不是用于访问全局变量(第三条不同的指令用于从封闭函数的作用域访问变量)。由于字节码是由编译器编写的,所以要使用的字节码指令需要在函数的编译时决定,而不是在调用函数时决定。指令的选择是很棘手的,但是对于这种模棱两可的代码:</p>
<pre><code>a = 1
def foo():
if bar():
a = 2
print(a)
</code></pre>
<p>对<code>print</code>调用的<code>a</code>的访问是使用读取全局变量<code>a</code>的字节码指令还是使用访问局部变量<code>a</code>的指令?编译器无法提前知道<code>bar</code>是否返回真值,因此没有可能的答案让函数在所有情况下都能工作。你知道吗</p>
<p>为了避免歧义,Python的设计人员选择变量的作用域在每个函数中都应该是常量(这样编译器就可以选择一条字节码指令并继续使用它)。也就是说,像<code>a</code>这样的名称可以引用局部或全局(或闭包单元),但在任何给定函数中只能引用其中一个。你知道吗</p>
<p>编译器默认使用局部变量(访问速度最快)作为函数代码中任何位置的赋值目标。由于内部函数与包含它们的函数同时编译,因此也可以在编译时检测到非本地查找并使用适当的指令。如果在局部作用域或封闭作用域中都找不到该名称,编译器将假定它是一个全局变量(现在还不需要定义)。<code>global</code>和<code>nonlocal</code>语句允许您显式地告诉编译器使用特定的作用域(重写它自己选择的作用域)。你知道吗</p>
<p>您可以使用标准库中的<code>dis</code>模块探索编译器在不同范围内处理变量查找的不同方式。<code>dis</code>模块将字节码反汇编成更可读的格式。尝试调用<code>dis.dis</code>函数,如下所示:</p>
<pre><code>a = 1
def load_global():
print(a) # access the global variable "a"
def load_fast():
a = 2
print(a) # access the local variable "a", which shadows the global variable
def closure():
a = 2
def load_dref():
print(a) # access the variable "a" from the enclosing scope
return load_dref
load_dref = closure() # both dis.dis(closure) and dis.dis(load_dref) are interesting
</code></pre>
<p>关于如何解释<code>dis.dis</code>的输出的全部细节超出了这个答案的范围(没有双关语),但是需要寻找的主要内容是<code>LOAD_...</code>字节码指令,这些指令将<code>(a)</code>作为它们的目标。在上面的不同函数中,您将看到三个不同的<code>LOAD_...</code>指令,对应于它们所读取的三种不同类型的作用域(每个函数都以相应的指令命名)。你知道吗</p>