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的变量和方法中。比较重要的有:

  1. co_code 字节码
  2. co_consts 常量池
  3. 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对象,除此之外还有

  1. *f_builtins 代码执行的内建命名空间
  2. *f_globals 代码执行的全局命名空间
  3. *f_locals 代码执行的局部命名空间

小结

通过这三个模块来剖析额字节码,涉及到很多python解释器的内容,更多细节待补充。

python 解析字节码的相关方法的更多相关文章

  1. 《python解释器源码剖析》第8章--python的字节码与pyc文件

    8.0 序 我们日常会写各种各样的python脚本,在运行的时候只需要输入python xxx.py程序就执行了.那么问题就来了,一个py文件是如何被python变成一系列的机器指令并执行的呢? 8. ...

  2. python 的 字节码 导入使用

    1. python 模块文件可以通过编译为字节码的形式: 名字:model.py x = def funt(): import model print(model.x) x = "zhang ...

  3. python查看字节码

    查看字节码可以帮助我们更好的理解python的执行流程 查看字节码列表 import opcode for op in range(len(opcode.opname)): print('0x%.2X ...

  4. 从底层入手,解析字节码增强和Btrace应用

    这篇文章聊下字节码和相关的应用. 1.机器码和字节码 机器码(machine code),学名机器语言指令,有时也被称为原生码(Native Code),是电脑的CPU可直接解读的数据. 通常意义上来 ...

  5. Python 字节码是什么

    了解 Python 字节码是什么,Python 如何使用它来执行你的代码,以及知道它是如何帮到你的. 如果你曾经编写过 Python,或者只是使用过 Python,你或许经常会看到 Python 源代 ...

  6. Python字节码介绍

    了解 Python 字节码是什么,Python 如何使用它来执行你的代码,以及知道它是如何帮到你的.如果你曾经编写过 Python,或者只是使用过 Python,你或许经常会看到 Python 源代码 ...

  7. python 字节码死磕

    前言:  如果你跟我一样,对python的字节码感兴趣,想了解python的代码在内存中到底是怎么去运行的,那么你可以继续往下看,如果你是python新手,我建议你移步它处,本文适合有点基础的pyth ...

  8. Python字节码与解释器学习

    参考:http://blog.jobbole.com/55327/ http://blog.jobbole.com/56300/ http://blog.jobbole.com/56761/ 1. 在 ...

  9. 从一道CTF题学习python字节码到源码逆向

    概述: 该题来源为2022爱春秋冬季赛ezpython,难度不是很大刚好适合我这样的萌新入门 题目: 3 0 LOAD_CONST 1 (204) 3 LOAD_CONST 2 (141) 6 LOA ...

  10. 深入理解 python 虚拟机:字节码教程(1)——原来装饰器是这样实现的

    深入理解 python 虚拟机:字节码教程(1)--原来装饰器是这样实现的 在本篇文章当中主要给大家介绍在 cpython 当中一些比较常见的字节码,从根本上理解 python 程序的执行.在本文当中 ...

随机推荐

  1. promise时效架构升级方案的实施及落地

    一.项目背景 为什么需要架构升级 promise时效包含两个子系统:内核时效计算系统(系统核心是时效计算)和组件化时效系统(系统核心是复杂业务处理以及多种时效业务聚合,承接结算下单黄金流程流量),后者 ...

  2. rust程序设计(5)结构体相关练习题| 附带解答

    题目 基础结构体练习: 创建一个名为Person的结构体,包含name(字符串类型)和age(整数类型)两个字段. 写一个函数,接收一个Person实例作为参数,并打印出这个人的名字和年龄. 结构体方 ...

  3. Jayway JsonPath-提取JSON文档内容的Java DSL

    介绍 JsonPath是一种能够提取部分JSON文档属性.对象.数组的语法,支持条件过滤.数学运算.字符串处理等功能.JsonPath与JSON文档就像 XPath 表达式与 XML 文档结合使用一样 ...

  4. MySQL-防止误删除的方案就是删除,看不见岂不就是删除了吗,所以就是把它隐藏起来。

    版权声明:原创作品,谢绝转载!否则将追究法律责任. ----- 作者:kirin 伪删除: 用update替代delete 1.添加状态列 ALTER TABLE student2 ADD state ...

  5. AntDesignBlazor示例——新建项目

    本示例是AntDesign Blazor的入门示例,在学习的同时分享出来,以供新手参考. 1. 开发环境 VS2022 17.8.2 .NET8 AntDesign 0.16.2 2. 学习目标 创建 ...

  6. python数据类型元组、列表、集合、字典相互嵌套

    系统 Windows 10 专业工作站版22H2 软件 python-3.9.6-amd64.exe 拓展库: jupyter==1.0.0 notebook==7.0.6 1.元组嵌套 1.1 元组 ...

  7. [NOI online2022普及A] 王国比赛

    题目描述 智慧之王 Kri 统治着一座王国. 这天 Kri 决定举行一场比赛,来检验自己大臣的智慧. 比赛由 \(n\) 道判断题组成,有 \(m\) 位大臣参加.现在你已经知道了所有大臣的答题情况, ...

  8. 新版本下如何通过外部网络访问wsl

    众所周知,wsl2是windows下的linux子系统,并且采用类似于虚拟机NAT的管理方式.一般情况下,外部网络很难直接访问到wsl上的服务,除非使用端口转发.而现在,微软更新了wsl 2.0.0, ...

  9. Python笔记二之多线程

    本文首发于公众号:Hunter后端 原文链接:Python笔记二之多线程 这一篇笔记介绍一下在 Python 中使用多线程. 注意:以下的操作都是在 Python 3.8 版本中试验,不同版本可能有不 ...

  10. @Value是个什么东西

    对注解不了解的可以看一下: Java注解,看完就会用 首先我们要明确: @Value 是 Spring 框架的注解. 它有什么作用呢? 作用 @Value 通过注解将常量.配置文件中的值.其他bean ...