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 程序的执行.在本文当中 ...
随机推荐
- promise时效架构升级方案的实施及落地
一.项目背景 为什么需要架构升级 promise时效包含两个子系统:内核时效计算系统(系统核心是时效计算)和组件化时效系统(系统核心是复杂业务处理以及多种时效业务聚合,承接结算下单黄金流程流量),后者 ...
- rust程序设计(5)结构体相关练习题| 附带解答
题目 基础结构体练习: 创建一个名为Person的结构体,包含name(字符串类型)和age(整数类型)两个字段. 写一个函数,接收一个Person实例作为参数,并打印出这个人的名字和年龄. 结构体方 ...
- Jayway JsonPath-提取JSON文档内容的Java DSL
介绍 JsonPath是一种能够提取部分JSON文档属性.对象.数组的语法,支持条件过滤.数学运算.字符串处理等功能.JsonPath与JSON文档就像 XPath 表达式与 XML 文档结合使用一样 ...
- MySQL-防止误删除的方案就是删除,看不见岂不就是删除了吗,所以就是把它隐藏起来。
版权声明:原创作品,谢绝转载!否则将追究法律责任. ----- 作者:kirin 伪删除: 用update替代delete 1.添加状态列 ALTER TABLE student2 ADD state ...
- AntDesignBlazor示例——新建项目
本示例是AntDesign Blazor的入门示例,在学习的同时分享出来,以供新手参考. 1. 开发环境 VS2022 17.8.2 .NET8 AntDesign 0.16.2 2. 学习目标 创建 ...
- python数据类型元组、列表、集合、字典相互嵌套
系统 Windows 10 专业工作站版22H2 软件 python-3.9.6-amd64.exe 拓展库: jupyter==1.0.0 notebook==7.0.6 1.元组嵌套 1.1 元组 ...
- [NOI online2022普及A] 王国比赛
题目描述 智慧之王 Kri 统治着一座王国. 这天 Kri 决定举行一场比赛,来检验自己大臣的智慧. 比赛由 \(n\) 道判断题组成,有 \(m\) 位大臣参加.现在你已经知道了所有大臣的答题情况, ...
- 新版本下如何通过外部网络访问wsl
众所周知,wsl2是windows下的linux子系统,并且采用类似于虚拟机NAT的管理方式.一般情况下,外部网络很难直接访问到wsl上的服务,除非使用端口转发.而现在,微软更新了wsl 2.0.0, ...
- Python笔记二之多线程
本文首发于公众号:Hunter后端 原文链接:Python笔记二之多线程 这一篇笔记介绍一下在 Python 中使用多线程. 注意:以下的操作都是在 Python 3.8 版本中试验,不同版本可能有不 ...
- @Value是个什么东西
对注解不了解的可以看一下: Java注解,看完就会用 首先我们要明确: @Value 是 Spring 框架的注解. 它有什么作用呢? 作用 @Value 通过注解将常量.配置文件中的值.其他bean ...