4. 执行模型 — Python 文档

来自菜鸟教程
Python/docs/3.9/reference/executionmodel
跳转至:导航、​搜索

4. 执行模型

4.1. 程序的结构

Python 程序由代码块构成。 block 是作为一个单元执行的一段 Python 程序文本。 以下是块:模块、函数体和类定义。 以交互方式键入的每个命令都是一个块。 脚本文件(作为解释器的标准输入或指定为解释器的命令行参数的文件)是一个代码块。 脚本命令(在解释器命令行上使用 -c 选项指定的命令)是一个代码块。 从命令行使用 -m 参数作为顶级脚本(作为模块 __main__)运行的模块也是一个代码块。 传递给内置函数 eval()exec() 的字符串参数是一个代码块。

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


4.2. 命名和绑定

4.2.1. 名称绑定

名称 指的是对象。 名称由名称绑定操作引入。

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

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

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

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

程序文本中每次出现的名称都引用由以下名称解析规则建立的该名称的 binding


4.2.2. 名称解析

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

在代码块中使用名称时,将使用最近的封闭范围对其进行解析。 代码块可见的所有此类范围的集合称为块的 环境

如果根本找不到名称,则会引发 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__ 来找到的; 这应该是字典或模块(在后一种情况下使用模块的字典)。 默认情况下,在__main__模块中时,__builtins__为内置模块builtins; 当在任何其他模块中时,__builtins__builtins 模块本身字典的别名。


4.2.4. 与动态特征的交互

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

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

eval()exec() 函数无法访问用于解析名称的完整环境。 名称可以在调用者的本地和全局命名空间中解析。 自由变量不在最近的封闭命名空间中解析,而是在全局命名空间中解析。 1 exec()eval() 函数有可选参数来覆盖全局和本地命名空间。 如果只指定了一个命名空间,则两者都使用它。


4.3. 例外

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

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

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

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

异常由类实例标识。 except 子句的选择取决于实例的类:它必须引用实例的类或其基类。 实例可以被处理程序接收,并且可以携带有关异常情况的附加信息。

笔记

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


另请参阅 节中的 try 语句的描述raise 语句 节中的 try 语句 和 raise 语句。

脚注

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