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的命名空间。首先搜索全局命名空间。如果在那里没有找到名称,则搜索builtins命名空间。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解释器引发异常,当它检测到运行时错误(如除以零)。Python程序还可以通过raise语句显示地引发异常。异常处理器通过try ... except语句指定。这种语句的finally子句可以用来指定清除代码,它不处理异常,而是在前面的代码中无论有没有出现异常都会执行。

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

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

异常由类实例标识。except子句的选择依赖于类的实例:它必须引用实例的类或者其基类。实例可以由处理程序接收并且可以携带关于异常条件的附加信息。

注意

异常的消息不是Python API的一部分。他们的内容可能会从一个版本的Python更改为下一个没有警告,不应依赖于将在解释器的多个版本下运行的代码。

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

脚注

[1]出现此限制是因为在编译模块时,由这些操作执行的代码不可用。