Skip to main content

4. 执行模型

4.1. 程序的结构

Python程序是从代码块构建的。 block 是一段作为一个单元执行的Python程序文本。以下是块:模块,函数体和类定义。每个交互类型的命令是一个块。脚本文件(作为解释器的标准输入或作为解释器的命令行参数指定的文件)是代码块。脚本命令(在解释器命令行中使用’-C‘选项指定的命令)是一个代码块。传递给内置函数 eval()exec() 的字符串参数是一个代码块。

execution frame 中执行代码块。框架包含一些管理信息(用于调试),并确定在代码块执行完成后继续执行的位置和方式。

4.2. 命名和绑定

4.2.1. 绑定名称

Names 指对象。名称是通过名称绑定操作引入的。

以下构造绑定名称:形式参数到函数,import 语句,类和函数定义(这些绑定定义块中的类或函数名称),以及作为标识符的目标(如果发生在分配,for 循环标头中或 as 之后)在 with 语句或 except 子句中。 from ... import * 形式的 import 语句绑定导入的模块中定义的所有名称,除了以下划线开头的名称。此表单只能在模块级别使用。

del 语句中出现的目标也被视为绑定到此目的(虽然实际语义是解除绑定名称)。

每个赋值或导入语句都发生在由类或函数定义或模块级(顶层代码块)定义的块中。

如果一个名称在一个块中被绑定,它是该块的一个局部变量,除非声明为 nonlocalglobal。如果一个名称在模块级绑定,它是一个全局变量。 (模块代码块的变量是局部的和全局的。)如果变量在代码块中使用,但是在那里没有定义,它是一个 free variable

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

4.2.2. 名称解析

scope 定义块中名称的可见性。如果在块中定义了局部变量,则其范围包括该块。如果定义出现在功能块中,则作用域将扩展到定义的块中包含的任何块,除非包含的块引入了对名称的不同绑定。

当在代码块中使用名称时,使用最近的封闭范围来解析它。代码块可见的所有这样的作用域的集合称为块的 environment

当根本找不到名称时,会引发 NameError 异常。如果当前作用域是一个函数作用域,并且该名称引用一个尚未绑定到使用该名称的点的值的局部变量,则会引发 UnboundLocalError 异常。 UnboundLocalErrorNameError 的亚类。

如果在代码块中的任何地方发生名称绑定操作,则块中的名称的所有使用都被视为对当前块的引用。这可能导致在块被绑定之前在块中使用名称时出现错误。这个规则是微妙的。 Python缺少声明,允许名称绑定操作发生在代码块中的任何地方。可以通过扫描用于名称绑定操作的块的整个文本来确定代码块的局部变量。

如果 global 语句出现在块中,则在语句中指定的名称的所有使用都指的是该名称在顶级命名空间中的绑定。通过搜索全局命名空间(即包含代码块的模块的命名空间)和builtins命名空间(模块 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__ 来找到;这应该是一个字典或模块(在后一种情况下使用模块的字典)。默认情况下,在 __main__ 模块中,__builtins__ 是内置模块 builtins;当在任何其他模块中时,__builtins__builtins 模块本身的字典的别名。 __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. 例外

异常是打破代码块的正常控制流以便处理错误或其他异常条件的手段。在检测到错误的点处的 raised 是例外;它可以是由周围代码块或直接或间接调用发生错误的代码块的任何代码块的 handled

Python解释器在检测到运行时错误(例如除以零)时引发异常。 Python程序也可以使用 raise 语句显式地引发异常。异常处理程序使用 try ... except 语句指定。这样的语句的 finally 子句可以用于指定不处理异常的清除代码,但是在前面的代码中是否发生异常时执行。

Python使用错误处理的“终止”模型:异常处理程序可以找到发生了什么并在外层继续执行,但是它无法修复错误的原因并重试失败操作(除非重新输入错误的部分的代码从顶部)。

当根本没有处理异常时,解释器终止程序的执行,或者返回到其交互式主循环。在任一情况下,它打印一个堆栈回溯,除非异常是 SystemExit

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

注解

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

另见 try 声明 部分中 try 语句的描述和 raise 声明 部分中的 raise 语句。

脚注

[1]

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