Python 解释器在内部使用了一些特殊的类型,比如 code、frame、traceback、slice、function 等等。虽然这些类型主要是为了解释器内部使用而设计的,但是它们也被暴露给了用户。用户可以使用这些特殊类型来进行元编程、调试和其他高级用途。
一、代码对象
代码对象是 Python 解释器将源代码编译成的一组字节码指令,可以被 Python 虚拟机执行。它包含了函数默认值、注释、全局变量和常量等信息,但不包括函数名和函数定义所在的模块,因此它是一种独立的对象,可以在多个函数之间共享。在 Python 中,代码对象通过 compile() 函数得到,通过 exec() 或 eval() 函数执行。与函数对象不同,代码对象无法直接调用,只能作为函数或模块的一部分参与运行时。
特殊的只读属性:
- co_name 给出函数名称;co_qualname 给出完整限定函数名称;
- co_argcount 是位置参数的总数量(包括仅限位置参数和带有默认值的参数);
- co_posonlyargcount 是仅限位置参数的总数量(包括带有默认值的参数);
- co_kwonlyargcount 是仅限关键字参数的数量(包括带有默认值的参数);
- co_nlocals 是函数使用的局部变量的数量(包括参数);
- co_varnames 是一个包含局部变量名称的元组(以参数名称打头);
- co_cellvars 是一个包含被嵌套函数所引用的局部变量名称的元组;
- co_freevars 是一个包含自由变量名称的元组;
- co_code 是一个表示字节码指令序列的字符串;
- co_consts 是一个包含字节码所使用的字面值的元组;
- co_names 是一个包含字节码所使用的名称的元组;
- co_filename 是被编码代码所在的文件名;
- co_firstlineno 是函数第一行的编号;
- co_lnotab 是一个编码了从字节码偏移量到行号的映射的字符串;
- co_stacksize 是要求的栈大小;co_flags 是一个编码了用于解释器的多个旗标的整数。
以下是可用于 co_flags 的标志位定义:
- 如果函数使用 *arguments 语法来接受任意数量的位置参数,则 0x04 位被设置;
- 如果函数使用 **keywords 语法来接受任意数量的关键字参数,则 0x08 位被设置;
- 如果函数是一个生成器,则 0x20 位被设置。
未来特性声明 (from __future__ import division) 也使用 co_flags 中的标志位来指明代码对象的编译是否启用特定的特性: 如果函数编译时启用未来除法特性则设置 0x2000 位; 在更早的 Python 版本中则使用 0x10 和 0x1000 位。
co_flags 中的其他位被保留为内部使用。如果代码对象表示一个函数,co_consts 中的第一项将是函数的文档字符串,如果未定义则为 None。
codeobject.co_positions():返回一个包含代码对象中每条字节码指令的源代码位置的可迭代对象。
此迭代器返回包含 (start_line, end_line, start_column, end_column) 的元组。 其中第 i 个元组对应于编译为第 i 条指令的源代码位置。 列信息是给定源代码行从 0 开始索引的 utf-8 字节偏移量。
此位置信息可能会丢失。 可能发生这种情况下非详尽列表如下:
- 附带 -X no_debug_ranges 运行解释器;
- 在使用 -X no_debug_ranges 时加载一个已编译的 pyc 文件;
- 与人工指令相对应的位置元组;
- 由于具体实现专属的限制而无法表示的行号和列号。
当发生此情况时,元组的部分或全部元素可以为 None。
3.11 新版功能:备注此特性需要在代码对象中存储列位置,这可能会导致编译的 which may result in a small increase of disk usage of compiled Python 文件占用的磁盘空间或解释器占用的内存略有增加。 要避免存储额外信息和/或取消打印额外的回溯信息,可以使用 -X no_debug_ranges 命令行旗标或 PYTHONNODEBUGRANGES 环境变量。
二、帧对象
帧对象表示执行帧。它们可能出现在回溯对象中,还会被传递给注册跟踪函数。
特殊的只读属性:
- f_back 为前一堆栈帧 (指向调用者),如是最底层堆栈帧则为 None;
- f_code 为此帧中所执行的代码对象;
- f_locals 为用于查找本地变量的字典;
- f_globals 则用于查找全局变量; f_builtins 用于查找内置 (固有) 名称;
- f_lasti 给出精确指令 (这是代码对象的字节码字符串的一个索引)。
- 访问 f_code 会引发一个 审计事件 object.__getattr__,附带参数 obj 和 “f_code”。
特殊的可写属性: f_trace,如果不为 None,则是在代码执行期间调用各类事件的函数 (由调试器使用)。通常每个新源码行会触发一个事件 – 这可以通过将 f_trace_lines 设为 False 来禁用。
具体的实现 可能 会通过将 f_trace_opcodes 设为 True 来允许按操作码请求事件。请注意如果跟踪函数引发的异常逃逸到被跟踪的函数中,这可能会导致未定义的解释器行为。
f_lineno 为帧的当前行号 — 在这里写入从一个跟踪函数内部跳转的指定行 (仅用于最底层的帧)。调试器可以通过写入 f_lineno 实现一个 Jump 命令 (即设置下一语句)。
帧对象支持一个方法:
frame.clear()
此方法清除该帧持有的全部对本地变量的引用。而且如果该帧属于一个生成器,生成器会被完成。这有助于打破包含帧对象的循环引用 (例如当捕获一个异常并保存其回溯在之后使用)。如果该帧当前正在执行则会引发 RuntimeError。
三、回溯对象
回溯对象表示一个异常的栈跟踪记录。当异常发生时会隐式地创建一个回溯对象,也可能通过调用 types.TracebackType 显式地创建。
对于隐式地创建的回溯对象,当查找异常句柄使得执行栈展开时,会在每个展开层级的当前回溯之前插入一个回溯对象。当进入一个异常句柄时,栈跟踪将对程序启用。它可作为 sys.exc_info() 所返回的元组的第三项,以及所捕获异常的 __traceback__ 属性被获取。
当程序不包含可用的句柄时,栈跟踪会 (以良好的格式) 写入标准错误流;如果解释器处于交互模式,它也可作为 sys.last_traceback 对用户启用。
对于显式创建的回溯对象,则由回溯对象的创建者来决定应该如何链接 tb_next 属性来构成完整的栈跟踪。
特殊的只读属性:
- tb_frame 指向当前层级的执行帧;
- tb_lineno 给出发生异常所在的行号; tb_lasti 标示具体指令。
如果异常发生于没有匹配的 except 子句或有 finally 子句的 try 语句中,回溯对象中的行号和最后指令可能与相应帧对象中行号不同。访问 tb_frame 会引发一个 审计事件 object.__getattr__,附带参数 obj 和 “tb_frame”。特殊的可写属性: tb_next 为栈跟踪中的下一层级 (通往发生异常的帧),如果没有下一层级则为 None。
在 3.7 版更改: 回溯对象现在可以使用 Python 代码显式地实例化,现有实例的 tb_next 属性可以被更新。
四、切片对象
切片对象被用来表示 __getitem__() 方法所使用的切片。 该对象也可使用内置的 slice() 函数来创建。
特殊的只读属性:
- start 为下界;
- stop 为上界;
- step 为步长值;
- 各值如省略则为 None。
这些属性可具有任意类型。
切片对象支持一个方法:
slice.indices(self, length)
此方法接受一个整型参数 length 并计算在切片对象被应用到 length 指定长度的条目序列时切片的相关信息应如何描述。 其返回值为三个整型数组成的元组;这些数分别为切片的 start 和 stop 索引号以及 step 步长值。索引号缺失或越界则按照与正规切片相一致的方式处理。
五、静态方法对象
静态方法对象提供了一种胜过上文所述将函数对象转换为方法对象的方式。 静态方法对象是对任意其他对象的包装器,通常用来包装用户自定义的方法对象。 当从类或类实例获取一个静态方法对象时,实际返回的是经过包装的对象,它不会被进一步转换。 静态方法对象也是可调用对象。 静态方法对象可通过内置的 staticmethod() 构造器来创建。
六、类方法对象
类方法对象和静态方法一样是对其他对象的封装,会改变从类或类实例获取该对象的方式。类方法对象可通过内置的 classmethod() 构造器来创建。