4.2. 命名与绑定

4.2.1. 名称的绑定

名称 用于指代对象。 名称是通过名称绑定操作来引入的。

以下构造会绑定名称:传给函数的正式形参,import 语句,类与函数定义(这会在定义的代码块中绑定类或函数名称)以及发生以标识符为目标的赋值,for 循环的开头,或 with 语句和 except 子句的 as 之后。 import 语句的 from … import * 形式会绑定在被导入模块中定义的所有名称,那些以下划线开头的除外。 这种形式仅在模块层级上被使用。

del 语句的目标也被视作一种绑定(虽然其实际语义为解除名称绑定)。

每条赋值或导入语句均发生于类或函数内部定义的代码块中,或是发生于模块层级(即最高层级的代码块)。

如果名称绑定在一个代码块中,则为该代码块的局部变量,除非声明为 nonlocalglobal。 如果名称绑定在模块层级,则为全局变量。 (模块代码块的变量既为局部变量又为全局变量。) 如果变量在一个代码块中被使用但不是在其中定义,则为 自由变量

每个在程序文本中出现的名称是指由以下名称解析规则所建立的对该名称的 绑定

4.2.2. 名称的解析

作用域 定义了一个代码块中名称的可见性。 如果代码块中定义了一个局部变量,则其作用域包含该代码块。 如果定义发生于函数代码块中,则其作用域会扩展到该函数所包含的任何代码块,除非有某个被包含代码块引入了对该名称的不同绑定。

当一个名称在代码块中被使用时,会由包含它的最近作用域来解析。 对一个代码块可见的所有这种作用域的集合称为该代码块的 环境

当一个名称完全找不到时,将会引发 NameError 异常。 如果当前作用域为函数作用域,且该名称指向一个局部变量,而此变量在该名称被使用的时候尚未绑定到特定值,将会引发 UnboundLocalError 异常。 UnboundLocalErrorNameError 的一个子类。

如果一个代码块内的任何位置发生名称绑定操作,则代码块内所有对该名称的使用会被认为是对当前代码块的引用。 当一个名称在其被绑定前就在代码块内被使用时则会导致错误。 这个一个很微妙的规则。 Python 缺少声明语法,并允许名称绑定操作发生于代码块内的任何位置。 一个代码块的局部变量可通过在整个代码块文本中扫描名称绑定操作来确定。

如果 global 语句出现在一个代码块中,则所有对该语句所指定名称的使用都是在最高层级命名空间内对该名称绑定的引用。 名称在最高层级命名内的解析是通过全局命名空间,也就是包含该代码块的模块的命名空间,以及内置命名空间即 builtins 模块的命名空间。 全局命名空间会先被搜索。 如果未在其中找到指定名称,再搜索内置命名空间。 global 语句必须位于所有对其所指定名称的使用之前。

global 语句与同一代码块中名称绑定具有相同的作用域。 如果一个自由变量的最近包含作用域中有一条 global 语句,则该自由变量也会被当作是全局变量。

nonlocal 语句会使得相应的名称指向之前在最近包含函数作用域中绑定的变量。 如果指定名称不存在于任何包含函数作用域中则将在编译时引发 SyntaxError

模块的作用域会在模块第一次被导入时自动创建。 一个脚本的主模块总是被命名为 main

类定义代码块以及传给 exec()eval() 的参数是名称解析上下文中的特殊情况。 类定义是可能使用并定义名称的可执行语句。 这些引用遵循正常的名称解析规则,例外之处在于未绑定的局部变量将会在全局命名空间中查找。 类定义的命名空间会成为该类的属性字典。 在类代码块中定义的名称的作用域会被限制在类代码块中;它不会扩展到方法的代码块中 — 这也包括推导式和生成器表达式,因为它们都是使用函数作用域实现的。 这意味着以下代码将会失败:

  1. class A:
  2. a = 42
  3. b = list(a + i for i in range(10))

4.2.3. 内置命名空间和受限的执行

CPython implementation detail: 用户不应该接触 builtins,严格说来它属于实现细节。 用户如果要重载内置命名空间中的值则应该 importbuiltins 并相应地修改该模块中的属性。

与一个代码块的执行相关联的内置命名空间实际上是通过在其全局命名空间中搜索名称 builtins 来找到的;这应该是一个字典或一个模块(在后一种情况下会使用该模块的字典)。 默认情况下,当在 main 模块中时,builtins 就是内置模块 builtins;当在任何其他模块中时,builtins 则是 builtins 模块自身的字典的一个别名。

4.2.4. 与动态特性的交互

自由变量的名称解析发生于运行时而不是编译时。 这意味着以下代码将打印出 42:

  1. i = 10
  2. def f():
  3. print(i)
  4. i = 42
  5. f()

eval()exec() 函数没有对完整环境的访问权限来解析名称。 名称可以在调用者的局部和全局命名空间中被解析。 自由变量的解析不是在最近包含命名空间中,而是在全局命名空间中。 1exec()eval() 函数有可选参数用来重载全局和局部命名空间。 如果只指定一个命名空间,则它会同时作用于两者。