另一个 LOAD_NAME 指令去查找变量 my_variable,并将其推送到计算栈的顶部。
一个 LOAD_CONST 指令将一个整数 2 推送到计算栈的顶部。
一个 CALL_FUNCTION 指令。
CALL_FUNCTION 指令有2个参数,它表示 Python 需要在堆栈顶部弹出两个位置参数; 然后函数将在它上面进行调用,并且它也同时被弹出(关键字参数的函数,使用指令-CALL_FUNCTION_KW-类似的操作,并配合使用第三条指令CALL_FUNCTION_EX,它适用于函数调用涉及到参数使用 * 或 ** 操作符的情况)
一旦 Python 具备了这些,它将在调用堆栈上分配一个新的帧,填充到函数调用的本地变量,然后运行该帧内的 my_function 的字节码。一旦运行完成,帧将从调用堆栈中弹出,在原始帧中,my_function 的返回值将被推入到计算栈的顶部。
我们知道了这个东西了,也知道字节码了文件了,但是如何去使用字节码呢?ok不知道也没关系,接下来的时间我们所有的话题都将围绕字节码,在python有一个模块可以通过反编译Python代码来生成字节码这个模块就是今天要说的–dis模块。
dis模块的使用
dis模块包括一些用于处理 Python 字节码的函数,可以将字节码“反汇编”为更便于人阅读的形式。查看解释器运行的字节码还有助于优化代码。这个模块对于查找多线程中的竞态条件也很有用,因为可以用它评估代码中哪一点线程控制可能切换。参考源码Include/opcode.h,可以找到字节码的正式列表。详细可以看官方文档。注意不同版本的python生成的字节码内容可能不一样,这里我用的Python 3.8.
访问和理解字节码
输入如下内容,然后运行它:
函数 dis.dis() 将反汇编一个函数、方法、类、模块、编译过的 Python 代码对象、或者字符串包含的源代码,以及显示出一个人类可读的版本。dis 模块中另一个方便的功能是 distb()。你可以给它传递一个 Python 追溯对象,或者在发生预期外情况时调用它,然后它将在发生预期外情况时反汇编调用栈上最顶端的函数,并显示它的字节码,以及插入一个指向到引发意外情况的指令的指针。
它也可以用于查看 Python 为每个函数构建的编译后的代码对象,因为运行一个函数将会用到这些代码对象的属性。这里有一个查看 hello() 函数的示例:
代码对象在函数中可以以属性 __code__ 来访问,并且携带了一些重要的属性:
co_consts 是存在于函数体内的任意实数的元组
co_varnames 是函数体内使用的包含任意本地变量名字的元组
co_names 是在函数体内引用的任意非本地名字的元组
许多字节码指令–尤其是那些推入到栈中的加载值,或者在变量和属性中的存储值–在这些元组中的索引作为它们参数。
因此,现在我们能够理解 hello() 函数中所列出的字节码:
LOAD_GLOBAL 0:告诉 Python 通过 co_names (它是 print 函数)的索引 0 上的名字去查找它指向的全局对象,然后将它推入到计算栈。
LOAD_CONST 1:带入 co_consts 在索引 1 上的字面值,并将它推入(索引 0 上的字面值是 None,它表示在 co_consts 中,因为 Python 函数调用有一个隐式的返回值 None,如果没有显式的返回表达式,就返回这个隐式的值 )。
CALL_FUNCTION 1:告诉 Python 去调用一个函数;它需要从栈中弹出一个位置参数,然后,新的栈顶将被函数调用。
“原始的” 字节码–是非人类可读格式的字节–也可以在代码对象上作为 co_code 属性可用。如果你有兴趣尝试手工反汇编一个函数时,你可以从它们的十进制字节值中,使用列出 dis.opname 的方式去查看字节码指令的名字。
基本反汇编
函数dis()可以打印 Python 源代码(模块、类、方法、函数或代码对象)的反汇编表示。可以通过从命令行运行 dis 来反汇编 dis_simple.py 之类的模块。
输出按列组织,包含原始源代码行号,代码对象中的指令地址,操作码名称以及传递给操作码的任何参数。
对于简单的代码我们可以通过命令行的形式执行下面的命令:
python3-mdisdis_simple.py
输出
在这里源代码转换为4个不同的操作来创建和填充字典,然后将结果保存到一个局部变量。
首先解释每一行各列参数的含义:
以第一条指令为例:
第一列 数字(1)表示对应源代码的行数。
第二列(可选)指示当前执行的指令(例如,当字节码来自帧对象时)【这个例子没有】
第三列 一个标签,表示从之前的指令到此可能的JUMP 【这个例子没有】
第四列 数字是字节码中对应于字节索引的地址(这些是2的倍数,因为Python 3.6每条指令使用2个字节,而在以前的版本中可能会有所不同)指令LOAD_CONST在0位置。
第五列 指令本身对应的人类可读的名字这里是"LOAD_CONST"
第六列 Python内部用于获取某些常量或变量,管理堆栈,跳转到特定指令等的指令的参数(如果有的话)。
第七列 计算后的实际参数。
然后让我们看看这个过程:
由于 Python 解释器是基于栈的,所以前几步是用LOAD_CONST将常量按正确顺序放入到栈中,然后使用 BUILD_MAP 弹出要增加到字典的新键和值。用 STORE_NAME 将所得到的dict对象绑定名为my_dict.
反汇编函数
需要注意的是上面的命令行反编译的形式,不能自动的递归反编译函数,所以我们要使用在文件中导入dis的模式进行反编译,就像下面这样。
运行命令
python3dis_function.py
然后得到以下结果
要查看函数的内部,必须把函数传递到dis().因为这里打印的是函数内部的东西,所以没有显示函数的在外层的行编号,而是从2开始的。
下面解析下每一行指令的含义:
LOAD_GLOBAL 用来加载全局变量,包括指定函数名,类名,模块名等全局符号,这里是len函数,LOAD_FAST 一般加载局部变量的值,也就是读取值,用于计算或者函数调用传参等,这里就是传入参数args。
一般是先指定要调用的函数,然后压参数,最后通过 CALL_FUNCTION 调用。
STORE_FAST 保存值到局部变量。也就是把结果赋值给 STORE_FAST。
下面的print因为2个参数所以LOAD_FAST了2次,POP_TOP删除堆栈顶部(TOS)项。LOAD_CONST加载const变量,比如数值、字符串等等,这里因为是print所以值为None。
最后通过RETURN_VALUE来确定函数结尾。
要打印一个函数的总结信息我们可以使用dis的show_code的方法,它包含使用的参数和名的相关信息,show_code的参数就是这个函数对象,代码如下:
运行之后,结果如下
可以看到返回的内容有函数,方法,参数等信息。
反汇编类
上面我们知道了如何反汇编一个函数的内部,同样的我们也可以用类似的方法反汇编一个类。
我们看一个例子:
运行之和得到如下结果
从整体内容来看,结果分为了两部分Disassembly of __init__和Disassembly of __str__,Disassembly就是反汇编的意思。
首先分析__init__部分:
然后需要注意的一点是,方法是按照字母的顺序列出的,所以在部分,先看到name再看到self,但是他们都是 LOAD_FAST。
STORE_ATTR实现self.name = name。
然后LOAD_CONST一个None和RETURN_VALUE标志着函数结束。
接下来分析__str__部分:
最后
Python崛起并且风靡,因为优点多、应用领域广、被大牛们认可。学习 Python 门槛很低,但它的晋级路线很多,通过它你能进入机器学习、数据挖掘、大数据,CS等更加高级的领域。Python可以做网络应用,可以做科学计算,数据分析,可以做网络爬虫,可以做机器学习、自然语言处理、可以写游戏、可以做桌面应用…Python可以做的很多,你需要学好基础,再选择明确的方向。这里给大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!
👉Python所有方向的学习路线👈
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
👉Python必备开发工具👈
工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。
👉Python全套学习视频👈
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
👉实战案例👈
学python就与学数学一样,是不能只看书不做题的,直接看步骤和答案会让人误以为自己全都掌握了,但是碰到生题的时候还是会一筹莫展。
因此在学习python的过程中一定要记得多动手写代码,教程只需要看一两遍即可。
👉大厂面试真题👈
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!