4. 执行模型

4.1. 程序的结构

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

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

4.2. 名字与绑定

4.2.1. 名称的绑定

Names 参考对象。名称通过名称绑定操作引入。

以下构造绑定名称:函数的形参, import 语句、类和函数定义(这些定义绑定定义块中的类或函数名)以及作为标识符的目标(如果发生在赋值中); for 循环头或之后 as 在一个 with 语句或 except 条款。这个 import 表格的声明 from ... import * 绑定导入模块中定义的所有名称,以下划线开头的名称除外。此表单只能在模块级别使用。

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

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

如果名称绑定在块中,则它是该块的局部变量,除非声明为 nonlocalglobal . 如果名称在模块级别绑定,则它是全局变量。(模块代码块的变量为局部变量和全局变量。)如果变量用于代码块但未在代码块中定义,则它是 free variable .

程序文本中的每个名称引用 binding 根据以下名称解析规则建立的名称。

4.2.2. 名称的解析

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

在代码块中使用名称时,将使用最近的封闭范围来解析名称。对代码块可见的所有此类作用域的集合称为块的 environment .

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

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

如果 global 语句出现在一个块中,语句中指定的名称的所有使用都引用顶级命名空间中该名称的绑定。通过搜索全局命名空间(即包含代码块的模块的命名空间)和内置命名空间(模块的命名空间)来解析顶级命名空间中的名称。 builtins . 首先搜索全局命名空间。如果在那里找不到名称,则搜索内置命名空间。这个 global 语句必须位于该名称的所有用法之前。

这个 global 语句与同一块中的名称绑定操作具有相同的作用域。如果自由变量最近的封闭范围包含全局语句,则该自由变量将被视为全局变量。

这个 nonlocal 语句使相应的名称引用最近的封闭函数范围中以前绑定的变量。 SyntaxError 如果给定的名称不在任何封闭函数范围中,则在编译时引发。

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

类定义块和参数 exec()eval() 在名称解析的上下文中是特殊的。类定义是可以使用和定义名称的可执行语句。这些引用遵循名称解析的常规规则,但在全局命名空间中查找未绑定的局部变量时除外。类定义的命名空间成为类的属性字典。类块中定义的名称的范围仅限于类块;它不扩展到方法的代码块——这包括理解和生成器表达式,因为它们是使用函数范围实现的。这意味着以下内容将失败:

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

4.2.3. 内置和限制执行

CPython implementation detail: 用户不应触摸 __builtins__ ;这是一个严格的实现细节。想要覆盖内置命名空间中的值的用户应该 import 这个 builtins 模块化并适当修改其属性。

与代码块执行相关联的内置命名空间实际上是通过查找名称找到的。 __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. 例外情况

异常是一种打破代码块正常控制流的方法,以便处理错误或其他异常情况。例外情况是 提高 在检测到错误的点;可能 已处理 通过周围的代码块或直接或间接调用发生错误的代码块的任何代码块。

python解释器在检测到运行时错误(例如被零除)时引发异常。python程序还可以显式引发异常 raise 语句。异常处理程序是用指定的 tryexcept 语句。这个 finally 此类语句的子句可用于指定不处理异常但在前面的代码中是否发生异常时执行的清理代码。

python使用错误处理的“终止”模型:异常处理程序可以发现发生的情况并在外部级别继续执行,但它无法修复错误的原因并重试失败的操作(除非从顶部重新输入有问题的代码)。

当一个异常完全不被处理时,解释器终止程序的执行,或者返回到它的交互主循环。在这两种情况下,它都会打印一个堆栈回溯,除非异常是 SystemExit .

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

注解

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

另请参见 try 第节中的语句 这个 try 陈述raise 第节中的语句 这个 raise 陈述 .

脚注

1

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