4. 执行模型
4. 执行模型
4.1. 命名和绑定
名称是对象的引用。名称通过名称绑定操作引入。程序文本中名称的每一次出现都会引用名称的绑定,这种绑定在包含名称使用的最内层函数块中建立。
块是Python 程序文本的一个片段,作为一个单元执行。下面这些都是块:模块、函数体、类定义。交互式敲入的每一个命令都是块。一个脚本文件(作为解释器标准输入的文件或者在解释器的命令行中指定的第一个参数)是一个代码块。一个脚本命令(在解释器的命令行中以‘-c’选项指定的命令)是一个代码块。由内建函数execfile()读入的文件是一个代码块。传递给eval()内建函数和exec语句的字符串参数是一个代码块。由内建函数input()读入并执行的表达式是一个代码块。
代码块在执行帧中执行。帧包含一些管理信息(用于调试)并决定代码块执行完成之后从哪里以及如何继续执行。
定义域定义块中名称的可见性。如果局部变量定义在一个块中,它的定义域就包含这个块。如果定义出现在函数块中,定义域将扩展到该定义块中所包含的任何块,除非被包含的块为该名称引入一个不同的绑定。类代码块中定义的名称的定义域限制在类代码块中;它不会扩展到方法的代码块中 - 包括生成器表达式,因为它们使用函数的定义域实现。这意味着下面的写法将会失败:
class A:
a = 42
b = list(a + i for i in range(10))
当名称在代码块中使用时,使用包含它的最内层定义域解析。对于一个代码块可见的定义域集合称为该代码块的环境。
如果名称绑定在代码块中,那么它是该代码块的局部变量。如果名称绑定在模块的级别,那么它是一个全局变量。(模块代码块的变量既是局部的又是全局的。)如果一个变量在代码块中使用但是没有在那里定义,那么它是自由变量。
当完全找不到名称时,引发一个NameError异常。如果名称引用一个没有绑定的局部变量,引发一个UnboundLocalError异常。UnboundLocalError是NameError的子类。
下面的句法结构将会绑定名称:函数的形式参数、import语句、类和函数定义(绑定类或函数的名称于定义它们的代码块中)、出现在赋值语句中的目标是标识符、for循环的头部、except子句头部的第二个位置或者with语句中as的后面。from...import*形式的import语句绑定导入的模块中定义的所有名称,除了以下划线开头的那些。这种形式只可以在模块级别使用。
出现在del语句中的目标也被认为是这种目的的绑定(尽管真实的语义是取消名称的绑定)。解绑定一个被封闭的定义域引用的名称是非法的;编译器将会报一个SyntaxError。
每个赋值和导入语句出现在类或函数定义的代码块中或者出现在模块级别(顶级代码块)。
如果在代码块的任意地方出现名称绑定操作,那么代码块中该名称的所有使用将被当做对当前代码块的引用。当名称在一个代码块中绑定之前使用时将导致错误。这个规则是微妙的。Python缺少声明并允许名称绑定操作出现在代码块内任何地方。代码块的局部变量通过扫描代码块的全部文本的名称绑定操作决定。
如果代码块中出现global语句,那么所有使用该语句指明的名称都是引用顶级命名空间中的名称绑定。顶级命名空间中名称通过查找全局命名空间解析,即模块的命名空间包括代码块、内建的命名空间以及builtin模块的命名空间。首先查找全局命名空间。如果那里找不到名称,就会查找内建的命名空间。global语句必须在该名称的所有使用之前。
与执行的代码块相关联的内建命名空间实际上是通过查找它的全局命名空间中的builtins名称找到的;它应该是一个字典或者一个模块(如果是后一种情况,将使用模块的字典)。默认情况下,在main模块中时,builtins就是内建的模块builtin(注意:没有‘s’);在其它任何模块的时候,builtins是builtin模块自身的字典的别名。builtins可以被设置成一个用户创建的字典以创建一个严格执行的弱形式。
CPython实现细节:使用者不应该触动builtins;严格地讲它是实现细节。使用者如果想要覆盖内建命名空间中的变量应该导入该builtin(没有‘s’)模块并适当地修改它的属性。
模块的命名空间在模块第一次导入时自动创建。脚本的主模块总是被称为main。
global语句具有和同一个代码块中名称绑定操作相同的定义域。如果包含自由变量的最内层定义域包含一条global语句,那么这个自由变量被认为是一个全局变量。
类定义是一条可以使用和定义名称的可执行语句。这些引用遵循名称解析的正常规则。类定义的命名空间变成类的属性字典。类定义域中定义的名称在方法中不可见。
4.1.1. 与动态功能的交互
当与包含自由变量的嵌套定义域联合使用的时候,有几种Python语句是非法的情况。
如果变量在一个包含它的定义域中被引用,那么删除它的名称是非法的。在编译的时刻将会报告一个错误。
如果在函数中使用通配符形式的导入— import* —并且函数包含或者是一个带有自由变量的嵌套代码块,那么编译器将会引发一个SyntaxError。
如果函数中使用exec且函数包含或者是一个带有自由变量的嵌套代码块,那么编译器将会引发一个SyntaxError除非执行为exec显式指明局部命名空间。(换句话说,execobj是非法的,而execobjinns是合法的。)
eval()、execfile()和input()函数以及exec语句没有访问完整环境的权限来解析名称。名称可以在调用者的局部和全局命名空间中解析。自由变量不是在包含它们的最内层命名空间中解析,而是在全局命名空间中。[1]exec语句以及eval()和execfile()函数具有可选参数以覆盖全局和局部命名空间。如果只指明一个命名空间,则两个命名空间都会使用它。
4.2. 异常
异常是一种打断代码块的正常控制流程以处理错误或者其它意外情况的方法。异常在错误检测到的点引发;它可以通过包围它的代码块或者直接或间接调用发生错误的代码块的代码块处理。
Python解释器在检测到运行时错误(例如除0)时会引发一个异常。Python还可以通过raise语句显示地引发异常。异常处理器通过try ... except语句指定。这种语句的finally子句可以用来指定清除代码,它不处理异常,而是在前面的代码中无论有没有出现异常都会执行。
Python 异常处理使用“终止”模型:异常处理器可以查明发生了什么并在外层继续执行,但是它不可以修复错误的根源并重试失败的操作。(除非通过从顶层重新进入出错的代码片段)。
当一个异常没有被任何处理,那么解释器会终止程序的执行或者返回到其交互式的主循环。在任何一种情况下,它都会打印出一个栈回溯,除了异常是SystemExit的时候。
异常通过类的实例标识。except子句的选择依赖于类的实例:它必须引用实例的类或者其基类。实例可以通过处理器接收并且可以带有异常条件的额外信息。
异常也可以通过字符串标识,在这种情况下except子句通过对象的ID 选择。任意一个值可能会跟随标识字符串一起引发并传递给处理器。
注意
异常的消息不是Python API的一部分。它们的内容可能随着Python 版本不断地改变而没有警告,在多种不同版本的解释器下运行的代码不应该依赖这些内容。
另请参考try语句小节中的try语句和raise语句小节中的raise语句。
脚注
| [1] | 出现这种限制是因为通过这些操作执行的代码在模块编译的时候不可以访问。 | |-----|-----|