您现在的位置是:亿华云 > 数据库
字节码:分析 Python 执行的终极利器
亿华云2025-10-03 13:37:22【数据库】6人已围观
简介本文转载自微信公众号「Python中文社区」,作者巩庆奎。转载本文请联系Python中文社区公众号。一、什么是代码对象Code Object(代码对象)封装了 Python 虚拟机的字节码和虚拟机执行
本文转载自微信公众号「Python中文社区」,字节n执终极作者巩庆奎。码分转载本文请联系Python中文社区公众号。析P行
一、利器什么是字节n执终极代码对象
Code Object(代码对象)封装了 Python 虚拟机的字节码和虚拟机执行相关信息,可以把字节码称为 Python 虚拟机上的码分汇编语言。
学代码对象有什么用呢?析P行从其定义可知,字节码是利器编译后的 Python 代码,学习代码对象有助于我们理解 Python 虚拟机、字节n执终极编译过程、码分执行过程,析P行更加深刻理解 Python 语言特性和疑难点。利器在解决一些疑难杂症时,字节n执终极查看代码对象的码分字节码往往有事半功倍的效果。
二、析P行探索代码对象
Python 程序由代码块组成,代码块可以是模块、函数或类,也可以是脚本文件,还可以是python - c string和exec (string)、eval (string)中字符串的内容。
两种方法:
fun.__code__ 获取函数 fun 主体的代码对象 compile(source code,,exec)获取代码块 source code 的亿华云代码对象三、一个函数及其代码对象
我们定义一个函数fun(a,b),它完成简单的加法运算。
def fun(a,b): return a+b来看它的代码对象属性情况,我们重点关注以 co_ 开头的属性。
for attr in dir(fun.fun.__code__): if attr.startswith(co_): print("{ attr}:\t{ attrs}".format( attrs=getattr(attr=attr,fun.fun.__code__, attr)))输出为:
co_argcount: 2 co_cellvars: () co_code: b|\x00|\x01\x17\x00S\x00 co_consts: (None,) co_filename: G:\pythonCodeStudy\manuscript\0 bytecode\fun.py co_firstlineno: 1 co_flags: 67 co_freevars: () co_kwonlyargcount: 0 co_lnotab: b\x00\x01 co_name: fun co_names: () co_nlocals: 2 co_posonlyargcount: 0 co_stacksize: 2 co_varnames: (a, b)fun 函数主体的代码对象的属性意义如下:
co_argcount 函数形式参数个数,这个只有函数类型代码块的代码对象有,其它类型代码块没有该属性。 co_code 字节码指令序列,字节码都由操作码 opcode 和参数 opatg 组成的序列。 co_const 常量列表,列表内容包括如下: None 函数返回值,系统自带。 从前往后数所有字面常量:数字和字符串。 内嵌函数代码对象 code object。 内嵌函数的 qual_name 常量,如:outer..inner。 co_name 本函数的名字。 co_varnames 本函数 局部变量 ,不含被引用自由变量,包含形式参数和内嵌函数名。 co_names 本函数用到的非局部变量,也就是网站模板 全局变量、系统内置变量。 co_nlocals 本函数的局部变量个数。 co_cellvars cell 变量。 co_freevars 自由变量。 co_flag 代码对象的种类,比如协程、生成器等,其意义定义在 include/code.h 中。 co_lnotab 计算字节码偏移量代表的源代码行号的字节序列。两个字节序列为一个单位,指示两条源代码指令编译成的字节码之间偏移多少。 co_stacksize 执行字节码指令时,计算栈上最大的项目数,和函数参数个数有关。Python虚拟机是基于栈的机器,每步函数调用产生栈帧(stack frame)。每个栈帧包含计算栈和块栈。所有参数压入计算栈,调用时弹栈,计算后弹出结果,结束本栈帧。
上例中,我们没有详细解释 co_cellvars 和 co_freevars,下面详细解释。源码下载
3.1 co_cellvars 和 co_freevars
co_cellvars 和 co_freevars 是一个相对的概念。我们写一个嵌套函数来解释这两个概念:函数 outer 中,定义了一个变量 e,被内部嵌套函数 inner 引用;inner 函数内部使用了变量 e,但是并未在该块内定义之。
def outer(o1, o2=o2): e = enclose def inner(i1, i2=i2): print(e) return e return inner print(outer.__code__.co_cellvars) print(outer(i1).__code__.co_freevars)结果。
(e,) (e,)故此,我们知道:
co_cellvars 是被内部嵌套块(函数)引用的变量组成的元组。外部函数创建特殊的 cell 对象存储该变量,cell 对象的生存周期超过了定义它的外部函数。这句话理解就是:外部变量执行完之后,清理现场,它的变量都消失了,但 cell 对象不消失,仍然存在。这也就是所谓 * cell 变量* co_freevars 就块内使用,但是并未在该块内定义的变量(不含全局变量、内置变量)。也就是当前块(函数) 引用的外部 cell 变量 组成的元组,和上个 co_cellvars 是相对的概念这两个是一体两面的变量。
外部变量 outer 作用域里,创建了变量 e 以及变量 inner (函数)。 因为嵌套函数 inner 使用了外部变量 e ,所以在 outer 函数里, e 是作为 cell 变量,绑定到特殊的 cell 对象里。 这个特殊的 cell 对象和 inner 绑定在了一起,这样 outer 作用域消失的时候, inner 内部借由 cell 对象访问到了 e 这个变量,它对 inner 函数来说是(来自 cell 对象的)自由变量。掌握自由变量的概念,是编写带状态函数、装饰器的基础。
四、字节码细节
字节码看起来就像乱码,如上文 fun 函数的字节码:co_code : b |\ x00 |\ x01 \ x17 \ x00S \ x00 。
根据上文,字节码由一位操作码和一位参数组成的序列。让我们分析细节。
co_code [0]表示第一个操作码|,这是 ASCII 码 124 表示的字符,在 include/opcode.h 中,可以看到 124 是 LOAD_FAST 操作码,这是对局部变量列表进行的加载操作。其它类似的:比如 LOAD_CONST 就是对字面常量列表操作,LOAD_GLOBAL 是对全局变量操作。
如果操作码不带参数,参数可以省略。这里的第一个操作码的参数是 co_code[1] 为 0x00 。
因此,这个完整的字节码操作是把局部变量列表 co_varnames 的第 0x00 索引内容 a ,压入计算栈栈顶。
字节序列看起来比较费劲,让我们用 dis.dis(fun)来反汇编代码,得到字节码如下。
2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4 BINARY_ADD 6 RETURN_VALUE这样看起来就很简单了。
第一列 2 表示源代码的第二行,也就是return a+b这一条。 第二列的 0 表示该条操作码相对字节码开头的偏移量,LOAD_FAST 表示操作码,0 表示参数,(a)是由 dis 生成的,即局部变量元组第 0 个元素 a。本条将变量 a 压入计算栈。 以此类推第二条,将局部变量 b 压入计算栈。 第三条 BINARY_ADD 没有参数,它是求和,将 a 和 b 弹出,求和,结果压入计算栈栈顶。 第四条 RETURN_VALUE 弹出结果,结束本栈帧。Python 编译产生 pyc 文件:Python3 之前是在本地目录产生,之后是在 pycache 目录下。我们打开上例产生的 pyc 文件,使用十六进制查看,能明显发现,编译的字节码就直接在 PYC 文件里。
五、其它代码块代码对象
上例中,我们对函数进行反汇编,使用的是 dis.dis(fun)指令,这里的 fun 是函数。
第二部分说过得到代码对象有两种方法,当我们对其它代码对象进行 compile 时,实际是对该模块进行反编译。如果此时该代码块里有函数,只会产生代码对象,不会产生真正的函数对象。
比如如下语句:print(dis.dis(compile(def fun(a,b): return a+b, , exec))),输出如下。可见此时的函数只是一个代码对象,作为常量载入,MAKE_FUNCTION 后,赋值给 fun 局部变量。
1 0 LOAD_CONST 0 (<code object fun at 0x00A67B18, file ", line 1>) 2 LOAD_CONST 1 (fun) 4 MAKE_FUNCTION 0 6 STORE_NAME 0 (fun) 8 LOAD_CONST 2 (None) 10 RETURN_VALUE六、总结
代码对象封装了 Python 虚拟机的字节码和其它编译相关信息,可以把字节码称为 Python 虚拟机上的汇编语言。我们分析了自由变量和 cell 变量,掌握自由变量的概念,这是编写装饰器、带状态函数的基础。字节码由一位操作码和一位参数组成的序列,学习其细节,有助于我们理解 Python 的特性。可在分析变量作用域、闭包时作为强大的工具。
作者:巩庆奎,大奎,对计算机、电子信息工程感兴趣。gongqingkui at 126.com
很赞哦!(93)
下一篇: 七个常见数据中心迁移挑战