python 解析字节码的相关方法

python代码被解释器执行时分为两步走:
一、python编译器将代码编译成字节码
二、python虚拟机执行字节码
由于这两步是一起的,所以在python编程中很少能看到字节码。但是想要提高代码效率就必须知道一段python源码对应的字节码是怎么样的,拨开迷雾看本质。本文分析python常见的能编译成字节码相关的函数。
compile
compile() 函数是python的内置函数,功能是将python源代码编译为code对象或AST对象。
函数定义:
compile(source, filename, mode[, flags[, dont_inherit]])
参数说明:
- source:字符串或AST(Abstract Syntax Trees)对象,表示需要进行编译的Python代码
- filename:指定需要编译的代码文件名称,如果不是从文件读取代码则传递一些可辨认的值(通常是用'')
- mode:用于标识必须当做那类代码来编译,规则如下:
如果source是由一个代码语句序列组成,则指定mode='exec';
如果source是由单个表达式组成,则指定mode='eval';
如果source是由一个单独的交互式语句组成,则指定mode='single'。 - 另外两个可选参数暂不做介绍
简单例子
ss = """
a = 100
b = 200
c = a + b
print(c)
"""
co = compile(ss, 'string', 'exec')
print(co)
print(dir(co))
exec(ss)
<code object <module> at 0x7f51c25e8810, file "string", line 2>
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
300
变量co就是一个字节码对象,通过dir(co)能够看到该字节码对象中有很多属性。code对象是一个对象,在CPyhon解释器中,该对象就是一个结构体,结构体如下:
typedef struct {
PyObject_HEAD /* 头部信息, 我们看到真的一切皆对象, 字节码也是个对象 */
int co_argcount; /* 可以通过位置参数传递的参数个数 */
int co_posonlyargcount; /* 只能通过位置参数传递的参数个数, Python3.8新增 */
int co_kwonlyargcount; /* 只能通过关键字参数传递的参数个数 */
int co_nlocals; /* 代码块中局部变量的个数,也包括参数 */
int co_stacksize; /* 执行该段代码块需要的栈空间 */
int co_flags; /* 参数类型标识 */
int co_firstlineno; /* 代码块在对应文件的行号 */
PyObject *co_code; /* 指令集, 也就是字节码, 它是一个bytes对象 */
PyObject *co_consts; /* 常量池, 一个元组,保存代码块中的所有常量。 */
PyObject *co_names; /* 一个元组,保存代码块中引用的其它作用域的变量 */
PyObject *co_varnames; /* 一个元组,保存当前作用域中的变量 */
PyObject *co_freevars; /* 内层函数引用的外层函数的作用域中的变量 */
PyObject *co_cellvars; /* 外层函数中作用域中被内层函数引用的变量,本质上和co_freevars是一样的 */
Py_ssize_t *co_cell2arg; /* 无需关注 */
PyObject *co_filename; /* 代码块所在的文件名 */
PyObject *co_name; /* 代码块的名字,通常是函数名或者类名 */
PyObject *co_lnotab; /* 字节码指令与python源代码的行号之间的对应关系,以PyByteObject的形式存在 */
//剩下的无需关注了
void *co_zombieframe; /* for optimization only (see frameobject.c) */
PyObject *co_weakreflist; /* to support weakrefs to code objects */
void *co_extra;
unsigned char *co_opcache_map;
_PyOpcache *co_opcache;
int co_opcache_flag;
unsigned char co_opcache_size;
} PyCodeObject;
可以看到该结构提中的有很多成员变量,这些成员变量也反应在co的变量和方法中。比较重要的有:
- co_code 字节码
- co_consts 常量池
- co_names 符号池
到这里我们能看到的还是code对象,并没有看到具体的字节码,co_code 是指向具体的字节码,但是由于是2进制,不便于查看。所有如果想看到纯粹的字节码,我们可以用下面的dis模块。
dis
dis是python的标准库模块,功能是将一段python代码编译成字节码指令。
python代码的执行过程分为两步:1.python解释器将代码编译成字节码;2.python虚拟机执行字节码。通常这两个步骤是一次性完成,所以我们看不到中间状态的字节码。而dis就可以将一段源码编译成字节码。
从上面的code对象中可以到看字节码是其一个成员变量co_code,字节码是被底层结构体PyCodeObject的成员co_code指向,那么dis可以取出字节码指令。
简单例子
import dis
ss = """
a = 100
b = 200
c = a + b
print(c)
"""
byte_str = dis.dis(ss)
ss这一段python代码的字节码就如下:
2 0 LOAD_CONST 0 (100)
2 STORE_NAME 0 (a)
3 4 LOAD_CONST 1 (200)
6 STORE_NAME 1 (b)
4 8 LOAD_NAME 0 (a)
10 LOAD_NAME 1 (b)
12 BINARY_ADD
14 STORE_NAME 2 (c)
5 16 LOAD_NAME 3 (print)
18 LOAD_NAME 2 (c)
20 CALL_FUNCTION 1
22 POP_TOP
24 LOAD_CONST 2 (None)
26 RETURN_VALUE
首先解释一下,python编译器编译过一段代码之后,生成的是code对象,对象就是compile中看到的结构体。这个对象中有字节码信息co_code,也有一些变量的信息,就是 co_consts 常量池 和 co_names 符号池。那么把这两个成员也打印出来。
(100, 200, None)
('a', 'b', 'c', 'print')
下面解释一下字节码。这段字节码就是python源码被编译之后的样子,也是最终被执行的对象。
2 0 LOAD_CONST 0 (100)
2 STORE_NAME 0 (a)
2 代表字节码对应的源码的行号
0 LOAD_CONST 代表字节码的行号和字节码指令
0 (100) 代表字节码的操作数
完整的说
2 0 LOAD_CONST 0 (100)
意思是:从常量池加载一个常量,加载的常量是序号为0,值为100
2 STORE_NAME 0 (a)
意思是:从符号池取一个符号,符号的序号是0,值为a,绑定到上一个操作的常量100。然后将a=100,存入命名空间中。
3 4 LOAD_CONST 1 (200)
6 STORE_NAME 1 (b)
和上面的类似。从常量池中取下标为1的常量,值为200,然后去符号池去下标为1的符号为b,绑定b=200,然后存入到命名空间中。
8 LOAD_NAME 0 (a)
10 LOAD_NAME 1 (b)
12 BINARY_ADD
14 STORE_NAME 2 (c)
8 LOAD_NAME 0 (a)
加载一个符号对应的值,符号是a,所以加载的值是100,
10 LOAD_NAME 1 (b)
加载符号b对应的值,值为200
12 BINARY_ADD
加法运算
14 STORE_NAME 2 (c)
从符号池去下标为2的元素,为符号c,然后绑定到上一步的结果300,然后存入到命名空间中。
inspect
inspect是python标准库模块,inspect模块四大功能:
1、类型检查(type checking)
2、获取源码(getting source code)
3、获取类和方法的参数信息(inspecting classes and functions)
4、解析堆栈(examining the interpreter stack)
其中对于分析代码字节码重要的包括:
inspect.stack() 获取调用者当前的堆栈信息
inspect.currentframe() 获取调用者当前Frame对象信息
简单例子
import inspect
def fun_demo():
a = 100
b = 200
c = a + b
s_ob = inspect.stack()
print(s_ob)
f_ob = inspect.currentframe()
print(f_ob)
print(dir(f_ob))
fun_demo()
[FrameInfo(frame=<frame at 0x7f9e884e4048, file 'inspect_demo.py', line 10, code fun_demo>, filename='inspect_demo.py', lineno=9, function='fun_demo', code_context=[' s_ob = inspect.stack()\n'], index=0), FrameInfo(frame=<frame at 0x7f9e8861a9f8, file 'inspect_demo.py', line 18, code <module>>, filename='inspect_demo.py', lineno=18, function='<module>', code_context=['fun_demo()\n'], index=0)]
<frame at 0x7f9e884e4048, file 'inspect_demo.py', line 13, code fun_demo>
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'f_back', 'f_builtins', 'f_code', 'f_globals', 'f_lasti', 'f_lineno', 'f_locals', 'f_trace', 'f_trace_lines', 'f_trace_opcodes']
Frame对象是python真正执行的对象,字节码对象code对象属于Frame对象。
我们都知道python解释器是模拟了真实的机器,所以需要有堆栈信息,而Frame就是python代码执行的堆栈信息的体现。其在Cpython中是一个结构体,如下:
typedef struct _frame {
PyObject_VAR_HEAD /* 可变对象的头部信息 */
struct _frame *f_back; /* 上一级栈帧, 也就是调用者的栈帧 */
PyCodeObject *f_code; /* PyCodeObject对象, 通过栈帧对象的f_code可以获取对应的PyCodeObject对象 */
PyObject *f_builtins; /* builtin命名空间,一个PyDictObject对象 */
PyObject *f_globals; /* global命名空间,一个PyDictObject对象 */
PyObject *f_locals; /* local命名空间,一个PyDictObject对象 */
PyObject **f_valuestack; /* 运行时的栈底位置 */
PyObject **f_stacktop; /* 运行时的栈顶位置 */
PyObject *f_trace; /* 回溯函数,打印异常栈 */
char f_trace_lines; /* 是否触发每一行的回溯事件 */
char f_trace_opcodes; /* 是否触发每一个操作码的回溯事件 */
PyObject *f_gen; /* 是否是生成器 */
int f_lasti; /* 上一条指令在f_code中的偏移量 */
int f_lineno; /* 当前字节码对应的源代码行 */
int f_iblock; /* 当前指令在栈f_blockstack中的索引 */
char f_executing; /* 当前栈帧是否仍在执行 */
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* 用于try和loop代码块 */
PyObject *f_localsplus[1]; /* 动态内存,维护局部变量+cell对象集合+free对象集合+运行时栈所需要的空间 */
} PyFrameObject;
其中 *f_code 就指向的是code对象,除此之外还有
- *f_builtins 代码执行的内建命名空间
- *f_globals 代码执行的全局命名空间
- *f_locals 代码执行的局部命名空间
小结
通过这三个模块来剖析额字节码,涉及到很多python解释器的内容,更多细节待补充。
python 解析字节码的相关方法的更多相关文章
- 《python解释器源码剖析》第8章--python的字节码与pyc文件
8.0 序 我们日常会写各种各样的python脚本,在运行的时候只需要输入python xxx.py程序就执行了.那么问题就来了,一个py文件是如何被python变成一系列的机器指令并执行的呢? 8. ...
- python 的 字节码 导入使用
1. python 模块文件可以通过编译为字节码的形式: 名字:model.py x = def funt(): import model print(model.x) x = "zhang ...
- python查看字节码
查看字节码可以帮助我们更好的理解python的执行流程 查看字节码列表 import opcode for op in range(len(opcode.opname)): print('0x%.2X ...
- 从底层入手,解析字节码增强和Btrace应用
这篇文章聊下字节码和相关的应用. 1.机器码和字节码 机器码(machine code),学名机器语言指令,有时也被称为原生码(Native Code),是电脑的CPU可直接解读的数据. 通常意义上来 ...
- Python 字节码是什么
了解 Python 字节码是什么,Python 如何使用它来执行你的代码,以及知道它是如何帮到你的. 如果你曾经编写过 Python,或者只是使用过 Python,你或许经常会看到 Python 源代 ...
- Python字节码介绍
了解 Python 字节码是什么,Python 如何使用它来执行你的代码,以及知道它是如何帮到你的.如果你曾经编写过 Python,或者只是使用过 Python,你或许经常会看到 Python 源代码 ...
- python 字节码死磕
前言: 如果你跟我一样,对python的字节码感兴趣,想了解python的代码在内存中到底是怎么去运行的,那么你可以继续往下看,如果你是python新手,我建议你移步它处,本文适合有点基础的pyth ...
- Python字节码与解释器学习
参考:http://blog.jobbole.com/55327/ http://blog.jobbole.com/56300/ http://blog.jobbole.com/56761/ 1. 在 ...
- 从一道CTF题学习python字节码到源码逆向
概述: 该题来源为2022爱春秋冬季赛ezpython,难度不是很大刚好适合我这样的萌新入门 题目: 3 0 LOAD_CONST 1 (204) 3 LOAD_CONST 2 (141) 6 LOA ...
- 深入理解 python 虚拟机:字节码教程(1)——原来装饰器是这样实现的
深入理解 python 虚拟机:字节码教程(1)--原来装饰器是这样实现的 在本篇文章当中主要给大家介绍在 cpython 当中一些比较常见的字节码,从根本上理解 python 程序的执行.在本文当中 ...
随机推荐
- 解决 Error L6915E 问题
出现以下错误: Error: L6915E: Library reports error: The semihosting __user_initial_stackheap cannot reliab ...
- 2023浙江省大学生信息安全竞赛决赛 Cry+Misc wp
搞到了一些附件,做做看难度如何. CRYPTO R_r 1.题目信息 查看代码 from Crypto.Util.number import * import random from gmpy2 im ...
- 自定义Graph Component:1-开发指南
可以使用自定义NLU组件和策略扩展Rasa,本文提供了如何开发自己的自定义Graph Component指南. Rasa提供各种开箱即用的NLU组件和策略.可以使用自定义Graph Compo ...
- Android app的暗黑模式适配实现
原文地址: Android app的暗黑模式适配实现 - Stars-One的杂货小窝 很久之前放在草稿箱的一篇简单笔记,是之前蓝奏云批量下载工具Android版本实现暗黑主题的适配记录 本文所说的这 ...
- 一个Blazor+WinForm+MAUI+PDA实现的条码比对系统
条码比对系统是由单机版桌面软件和Android版的PDA扫码软件组成,桌面软件采用Blazor与WinForm进行混合开发,PDA扫码软件采用MAUI进行开发,这个项目都是基于.NET技术进行构建,这 ...
- 面试题——为什么 Vue 中不要用 index 作为 key?(diff 算法详解)
前言 在vue中使用v-for时需要,都会提示或要求使用 :key,有的的开发者会直接使用数组的 index 作为 key 的值,但不建议直接使用 index作为 key 的值,有时我们面试时也会遇 ...
- js根据某属性对json数组分类
原数据: var arr = [ {name: '张三', age: 23, work: '计算机'}, {name: '王五', age: 29, work: '计算机'}, {name: '张兴' ...
- 企业ERP和泛微OA集成场景分析
轻易云数据集成平台(qeasy.cloud)为企业ERP和泛微OA系统提供了强大的互通解决方案,特别在销售.采购和库存领域的单据审批场景中表现出色.这些场景涉及到多个业务单据的创建和审批,以下是一些具 ...
- 金蝶云星空与泛微OA集成的方案落地与实践
打破信息孤岛,泛微OA集成的方案落地与实践 在现代企业内部,不同类型的业务系统和泛微OA平台层出不穷.企业需要找到一种高效的方法来整合和协同这些多样化的系统,同时将它们与泛微OA平台融合,以实现资源整 ...
- [ABC246A] Four Points
Problem Statement There is a rectangle in the $xy$-plane. Each edge of this rectangle is parallel to ...