作用域(概念)
在一个程序里,有可能在多个地方出现同样名字的变量,例如在所有函数之外用到一个全局变量,其名字是x,而函数f的参数中也有一个的名字是x。实际上这是两个变量,只是它们正好采用了同样的名字。

要准确理解程序的意义,需要确定程序里写出的每个名字实际上指的是那个变量(或其他东西)。而要严格地讨论变量名和变量的关系,就需要理解作用域的概念。

作用域

一个变量名的作用域(scope)是指程序代码中的一个部分,在这段代码中这个变量名就表示了某个特定的变量。

例如,一般说,函数的参数名的作用域就是这个函数的体,一个全局变量的作用域是整个文件,但有可能需要排除其作用范围内有同名局部变量定义的区域。

在 Python 里,一个变量被赋值,就是它有定义了,也有了名字约束。其第一次赋值的位置决定了它的作用域。函数参数的情况有些特殊,列在参数表里就是有了定义。

如果一个文件被作为主程序执行,其中在函数之外有定义的变量都是具有全局作用域的变量。如果该文件是作为模块被 import,有关情况见关于模块的讨论。

一个函数确定一个作用域,它的参数和所有局部变量以这个函数的体作为作用域。函数作用域嵌套在全局作用域里面。如果一个函数里有嵌套定义的函数,那些函数又确定了进一步嵌套的作用域。函数里还可能有更局部的作用域,下面讨论。

总之,程序里的作用域形成一种嵌套结构。如果程序里出现了一个变量的使用,就从这个使用位置向外逐层查找,确定其定义:它就是在从这个使用位置逐层向外找,最先找到的那个有定义的同名变量。

下面是一个例子:

x = 3
z = 2
def f (x) :
    def g () :
        y = 5
        ...x...
        ...
    def h () :
        def r () :
            ...y...
            ...
        ...z...
        ...
    y = 5
    ...
这里有全局变量的 x 和 z,而函数 f 又以 x 为参数,所以在 f 的体里出现的 x 永远不会是全局的 x(除非有用 global 说明的 x)。在函数 g 里有 y 的赋值,所以 g 的函数体里的 y 都是这个局部的 y,而其中用到的 x(假设没赋值)就是其外围函数 f 的参数 x。注意在 f 的体里有对 y 的赋值,这就定义了一个局部的 y。在 f 里嵌套在内层函数 h 里的函数 r 的体里使用了变量 y,如果它没有局部定义且在 h 里也没有定义,那么使用的就是 f 里定义的那个 y。在 h 的函数体里使用了 z,如果 h 和 f 里都没定义 z,用到就是全局的 z。

注1:函数里的 global 语句用于说明一个或几个变量是全局变量,它强行建立明确的变量使用关系。函数里的 nonlocal 语句说明一个或几个变量不是局部的,要求从当前函数的外围函数开始查找名字的定义(而不是从这个函数本身开始查找)。

注2:虽然按照 Python 的作用域规则,上面这么复杂的嵌套结构中虽然有很多重名情况,每个变量的使用都可以正确确定其定义。但在实际中这样的程序是不好的,因为它的意义很难理解。我们应尽可能减少程序里变量重名的情况,以便不影响程序的可读性。

Python 的作用域概念解释了可能出现的各种复杂情况,理解这些,不是为了让我们(作为程序员)去写包含复杂重名情况的嵌套作用域,而是为了:

  1. Python 系统为每个合法程序提供一种正确的解释;
  2. 了解作用域规则可能导致的复杂情况,写出正确且容易理解的程序;
  3. 帮助我们阅读和理解虽然复杂但也正确的程序。其他人可能写出复杂的情况,我们有可能需要理解其意义。

其他作用域问题

Python 里还有一些结构也引进了变量。包括: for 语句头部使用的循环变量,其作用域是这个 for 语句所在的函数。下面是一个例子:
>>> def f () :
	print(i)
	for i in range(5) :
		print(i)
	print(i)
>>> f()
Traceback (most recent call last):
  File "", line 1, in 
    f()
  File "", line 2, in f
    print(i)
UnboundLocalError: local variable 'i' referenced before assignment
这里出错,是因为系统认为第一个 print 语句使用了还没有赋值的 i。

在表等数据类型的生成式表示中的 for 片段里引进的变量,其作用域是这个生成式的基本表达式和位于这个 for 片段之后的那个部分。看下面两个例子:

>>> [i1 + i2 for i1 in range(i2) for i2 in range(4)]
Traceback (most recent call last):
  File "", line 1, in 
    [i1 + i2 for i1 in range(i2) for i2 in range(4)]
NameError: name 'i2' is not defined
在第一个 for 片段里 i2 没有定义。但下面生成式没问题:
>>> [i1 + i2 for i1 in range(4) for i2 in range(i1 + 2)]
[0, 1, 1, 2, 3, 2, 3, 4, 5, 3, 4, 5, 6, 7]
因为在第二个 for 片段里 i1 已经有定义了。
本页及相关页面(除另声明者外)由裘宗燕创建维护,可自由用于各种学习活动。其他使用需得到作者许可。