4. 执行模型

4.1. 程序的结构

Python程序是由代码块构造起来的。一个代码块是一段作为一个整体执行的Python程序文本。下面的这些都是代码块:一个模块、一个函数体、一个类定义。交互输入的每个命令都是一个代码块。一个脚本文件(作为解释器标准输入或者指明为解释器的命令行参数的文件)是一段代码块。一个脚本命令(解释器命令行上用‘-c‘选项指定的命名)是一段代码块。传递给内建函数eval()exec()的字符串参数是一个代码块。

一个代码块在一个执行帧中执行。一个帧包含一些管理信息(用于调试)),并决定代码块执行结束后从哪里以及如何继续执行。

4.2. 命名和绑定

4.2.1. 名称的绑定

名称引用对象。名称由名称绑定操作引入。

下面结构将会绑定名称:函数的形式参数、import语句、类和函数定义(绑定类或函数的名称于定义它们的代码块中)、出现在赋值语句中的目标标识符、for循环的头部、with语句或者except子句中as后面的内容。from ... import *形式的import语句绑定导入的模块中定义的所有名称,除了以下划线开头的那些。这种形式只能在模块级别使用。

发生在 del 语句的目标也被认为是绑定为此目的 (尽管实际语义要解除绑定的名称)。

每个赋值和导入语句出现在类或函数定义的代码块中或者出现在模块级别(顶级代码块)。

如果名称在一个代码块中绑定,那么它是该代码块的一个局部变量,除非声明为nonlocal或者global如果一个名称绑定在模块级别,那么它是一个全局变量。(定义在模块中的变量既是局部变量也是全局变量。)如果一个变量在代码块中只使用但未定义,那么它是一个自由变量

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

4.2.2. 名称的解析

作用域定义一个代码块中名称的可见性。如果一个局部变量在一个代码块中定义,那么它的作用域包括那个代码块。如果定义出现在函数代码块中,那么其作用域扩展到这个函数代码块中包含的任何代码块,除非某个被包含的代码块为该名称引入一个不同的绑定。

当一个名称在代码块中使用时,它使用包含它最近的作用域解析。对于一个代码块所有可见作用域的集合称做代码块的环境

当一个名称完全找不到是,将引发一个NameError异常。如果当前的作用域是一个函数作用域,而且名称引用一个局部变量,这个变量在该名称使用的时候还没有绑定到一个值,则引发一个UnboundLocalError异常。UnboundLocalErrorNameError的子类。

如果在代码块的任意地方出现名称绑定操作,那么代码块中该名称的所有使用将被当做对当前代码块的引用。当名称在一个代码块中绑定之前使用时将导致错误。这个规则是微妙的。Python缺少声明并允许名称绑定操作出现在代码块内任何地方。代码块的局部变量通过扫描代码块的全部文本的名称绑定操作决定。

如果global语句出现在代码块内,在语句中指定的名称的所有引用都是指该名称在的顶级命名空间中的绑定。名称在顶级命名空间中的解析通过搜索全局命名空间,即包含该代码块的模块的命名空间,和内建的命名空间——模块builtins的命名空间。首先查找全局命名空间。如果那里找不到名称,就会查找内建的命名空间。global语句必须位于该名称的所有引用之前。

global语句的作用域与同一代码块中的名称绑定操作相同。如果包含自由变量的最内层定义域包含一条global语句,那么这个自由变量被认为是一个全局变量。

nonlocal语句使得对应的名称引用在最靠近的包含它的函数的作用域中绑定的变量。如果给定的名称在任何包含它的函数的作用域中都找不到,则在编译时刻引发SyntaxError

模块的命名空间在模块第一次导入时自动创建。脚本的主模块始终叫做__main__

类定义以及exec()eval()的参数在名称解析的上下文中比较特殊。类定义是一条可以使用和定义名称的可执行语句。这些引用遵循正常的名称解析规则,除了一个例外,就是未绑定的局部变量在全局作用域中查找。类定义的命名空间变成类的属性字典。在类代码块中定义的名称的作用域限制在类代码块中;它不会延伸到方法的代码块中 —— 包括解析式和生成器表达式,因为它们是使用函数作用域实现的。也就是说下面这段代码执行会失败:

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

4.2.3. Builtins和受限的执行

与代码块执行关联的内建命名空间实际上是通过在全局命名空间中查找__builtins__找到的;它应该是一个字典或一个模块(在后一种情况下,使用模块的字典)。默认情况下,在__main__模块中时,__builtins__是内建模块builtins;在任何其它模块中时,__builtins__builtins模块自身的字典的别名。可以设置__builtins__为一个用户创建的字典来创建一个弱形式的受限的执行。

CPython的实现细节:用户不会接触到__builtins__;严格地讲,它只是一个实现细节。想要覆盖builtins命名空间中的值的用于应该import builtins模块并正确地修改它的属性。

4.2.4. 与动态功能的交互

自由变量的名称解析发生在运行时刻,不是在编译时刻。这意味着下面的代码将打印42:

i = 10
def f():
    print(i)
i = 42
f()

当与包含自由变量的嵌套定义域联合使用的时候,有几种Python语句是非法的情况。

如果变量在一个包含它的定义域中被引用,那么删除它的名称是非法的。在编译的时刻将会报告一个错误。

eval()exec()函数没有访问完整环境的权限来解析名称。名称可以在调用者的局部和全局命名空间中解析。自由变量不是在包含它们的最内层命名空间中解析,而是在全局命名空间中。[1]exec()eval()函数具有可选参数以覆盖全局和局部命名空间。如果只指明一个命名空间,则两个命名空间都会使用它。

4.3. 异常

异常是一种打断代码块的正常控制流程以处理错误或者其它异常条件的方法。异常在错误检测到的点引发;它可以通过包围它的代码块或者直接或间接调用发生错误的代码块的代码块处理

Python解释器在检测到运行时错误(例如除0)时会引发一个异常。Python程序还可以通过raise语句显示地引发异常。异常处理器通过try ... except语句指定。这种语句的finally子句可以用来指定清除代码,它不处理异常,而是在前面的代码中无论有没有出现异常都会执行。

Python异常处理使用“终止”模型:异常处理器可以查明发生了什么并在外层继续执行,但是它不可以修复错误的根源并重试失败的操作(除非通过从顶层重新进入出错的代码片段)。

当一个异常没有被任何处理,那么解释器会终止程序的执行或者返回到其交互式的主循环。在任何一种情况下,它都会打印出一个栈回溯,除了异常是SystemExit的时候。

异常通过类的实例标识。except子句的选择依赖于类的实例:它必须引用实例的类或者其基类。实例可以通过处理器接收并且可以带有异常条件的额外信息。

注意

异常的消息不是Python API的一部分。它们的内容可能随着Python 版本不断地改变而没有警告,在多种不同版本的解释器下运行的代码不应该依赖这些内容。

另请参考try语句小节中的try语句和raise语句小节中的raise语句。

脚注

[1]出现这种限制是因为通过这些操作执行的代码在模块编译的时候不可以访问。