IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> Python字节码分析(一) -> 正文阅读

[Python知识库]Python字节码分析(一)

简介👹

非常高兴大家能够订阅这个专栏,在这里我将会给大家分享一些Python相关源码的剖析
在接下来的这段日子里,我会一同带各位pythonista探索Python的奥秘


Pyc文件🐶
  • 简介🐶

当在文件被当成模块导入时才会生成pyc文件, pyc文件是Python的字节码文件, Python文件在经过解释器解释后生成字节码对象PyCodeObject,然后持久化到pyc中

  • 作用🐶

当多次运行程序时,不需要重新对导入的模块进行重新解释(检查pyc没有过期,即字节码文件的修改时间是否与脚本一致), 可以加快程序的运行

  • 位置🐶

pyc文件都被放到__pycache__文件夹下, pyc文件名格式是文件名.python解释器类型-解释器版本, 特比说明的是可以用pyc文件替换py文件,它们功能上是相同的

pyc文件格式如: classdemo.cpython-39.pyc

  • 如何避免生成pyc文件🐶
  1. python -B filename.py
  2. 环境PYTHONDONTWRITECODE变量设置成非空字符串
  3. 以上两种方法等价

你通常不需要关注__pycache__,在一些版本控制中如git,一般忽略提交__pycache__文件,
可以在.gitignore文件中加入**__pycache__来忽略


dis module and opcode module 🐕
  • dis module 🐕

dis module
可以反汇编模块、类、方法、函数、生成器和异步生成器等code object(
Disassemble module, classes, methods, functions, generator, async generator,
a string of source code or a byte sequence of row bytecode)
这些指令都定义在cpython/Include/opcode.h

字节码是CPython的解释器的实现细节, 不同虚拟机种类(CPython, IPython, Pypy, JPython等)和版本号
的指令可能不同,因此不应该考虑使用dis模块来跨Python VM和Python版本
CPython3.6开始每个指令使用2个字节, 之前版本占用字节数是根据版本变化的

/* Instruction opcodes for compiled code */
#define POP_TOP                   1
#define ROT_TWO                   2
#define ROT_THREE                 3
#define DUP_TOP                   4
#define DUP_TOP_TWO               5
#define ROT_FOUR                  6
#define NOP                       9
#define UNARY_POSITIVE           10
#define UNARY_NEGATIVE           11
#define UNARY_NOT                12
#define UNARY_INVERT             15
#define BINARY_MATRIX_MULTIPLY   16
#define INPLACE_MATRIX_MULTIPLY  17
#define BINARY_POWER             19
#define BINARY_MULTIPLY          20
#define BINARY_MODULO            22
#define BINARY_ADD               23
#define BINARY_SUBTRACT          24
#define BINARY_SUBSCR            25
#define BINARY_FLOOR_DIVIDE      26
#define BINARY_TRUE_DIVIDE       27
#define INPLACE_FLOOR_DIVIDE     28
#define INPLACE_TRUE_DIVIDE      29
#define RERAISE                  48
#define WITH_EXCEPT_START        49
#define GET_AITER                50
#define GET_ANEXT                51
#define BEFORE_ASYNC_WITH        52
#define END_ASYNC_FOR            54
#define INPLACE_ADD              55
#define INPLACE_SUBTRACT         56
#define INPLACE_MULTIPLY         57
#define INPLACE_MODULO           59
#define STORE_SUBSCR             60
#define DELETE_SUBSCR            61
#define BINARY_LSHIFT            62
#define BINARY_RSHIFT            63
#define BINARY_AND               64
#define BINARY_XOR               65
#define BINARY_OR                66
#define INPLACE_POWER            67
#define GET_ITER                 68
#define GET_YIELD_FROM_ITER      69
#define PRINT_EXPR               70
#define LOAD_BUILD_CLASS         71
#define YIELD_FROM               72
#define GET_AWAITABLE            73
#define LOAD_ASSERTION_ERROR     74
#define INPLACE_LSHIFT           75
#define INPLACE_RSHIFT           76
#define INPLACE_AND              77
#define INPLACE_XOR              78
#define INPLACE_OR               79
#define LIST_TO_TUPLE            82
#define RETURN_VALUE             83
#define IMPORT_STAR              84
#define SETUP_ANNOTATIONS        85
#define YIELD_VALUE              86
#define POP_BLOCK                87
#define POP_EXCEPT               89
#define HAVE_ARGUMENT            90
#define STORE_NAME               90
#define DELETE_NAME              91
#define UNPACK_SEQUENCE          92
#define FOR_ITER                 93
#define UNPACK_EX                94
#define STORE_ATTR               95
#define DELETE_ATTR              96
#define STORE_GLOBAL             97
#define DELETE_GLOBAL            98
#define LOAD_CONST              100
#define LOAD_NAME               101
#define BUILD_TUPLE             102
#define BUILD_LIST              103
#define BUILD_SET               104
#define BUILD_MAP               105
#define LOAD_ATTR               106
#define COMPARE_OP              107
#define IMPORT_NAME             108
#define IMPORT_FROM             109
#define JUMP_FORWARD            110
#define JUMP_IF_FALSE_OR_POP    111
#define JUMP_IF_TRUE_OR_POP     112
#define JUMP_ABSOLUTE           113
#define POP_JUMP_IF_FALSE       114
#define POP_JUMP_IF_TRUE        115
#define LOAD_GLOBAL             116
#define IS_OP                   117
#define CONTAINS_OP             118
#define JUMP_IF_NOT_EXC_MATCH   121
#define SETUP_FINALLY           122
#define LOAD_FAST               124
#define STORE_FAST              125
#define DELETE_FAST             126
#define RAISE_VARARGS           130
#define CALL_FUNCTION           131
#define MAKE_FUNCTION           132
#define BUILD_SLICE             133
#define LOAD_CLOSURE            135
#define LOAD_DEREF              136
#define STORE_DEREF             137
#define DELETE_DEREF            138
#define CALL_FUNCTION_KW        141
#define CALL_FUNCTION_EX        142
#define SETUP_WITH              143
#define EXTENDED_ARG            144
#define LIST_APPEND             145
#define SET_ADD                 146
#define MAP_ADD                 147
#define LOAD_CLASSDEREF         148
#define SETUP_ASYNC_WITH        154
#define FORMAT_VALUE            155
#define BUILD_CONST_KEY_MAP     156
#define BUILD_STRING            157
#define LOAD_METHOD             160
#define CALL_METHOD             161
#define LIST_EXTEND             162
#define SET_UPDATE              163
#define DICT_MERGE              164
#define DICT_UPDATE             165

dis.dis 🐕
  • 函数原型🐕

dis.dis(x=None, *, file=None, depth=None)

参数x

x是反汇编的内容(模块、类、方法、函数等等)
如果是模块,则反汇编所有函数
如果是类,则反汇编所有方法, 包括类方法和静态方法
如果是code object或者是原始字节码序列, 那么每条字节码指令都会打印一行内容

参数file

这个是将汇编结果写入的流中, 默认是sys.stdout, 否则写入给定的文件对象

参数depth

dis方法会默认反汇编嵌入的code objects(表达式代码,生成器表达式和嵌套的函数以及关于嵌套类的代码)
限制递归深度, 默认是None,表示不限制
depth如果是0,则表示不递归


opcode module 👻

cpython/Lib/opcode.py
这里我们只看opcode.opname和opcode.opmap
opcode.opname是所有256个操作码(1个字节最大支持数量,实际上有119个))
opcode.opmap是opcode.opname操作码: 索引组成的字典

opcode.py中使用def_op(name, op)对opmap和opcode进行初始化
opname = ['<%r>' % (op,) for op in range(256)] 这行代码是将256维度数组的元素全部重置成<0-255>

#!/usr/bin/python
# -*- coding:utf-8 -*-
import opcode


if __name__ == '__main__':
    print(opcode.opname)
    print(opcode.opmap)
    print(len(opcode.opmap))
    print(len([item for item in opcode.opname if not item.startswith("<")]))

bytecode analysis(字节码分析)🤡
如何查看字节码🤡
  • 使用dis module来查看字节码
#!/usr/bin/python
# -*- coding:utf-8 -*-
import dis
from types import CodeType


def example():
    y = 1.0
    for _ in range(20):
        y = y * 1.5

    return y


if __name__ == '__main__':
    # dump out a disassembly of the code in example function
    print(hasattr(example, '__code__'))
    print(isinstance(example.__code__, CodeType))
    dis.dis(example)

如上代码的运行结果
我们使用dis.dis传入example这个对象, 这个对象的type是code类型, 它的一个属性是__code__对象, 这表示code object.

True
True
  8           0 LOAD_CONST               1 (1.0)
              2 STORE_FAST               0 (y)

  9           4 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               2 (20)
              8 CALL_FUNCTION            1
             10 GET_ITER
        >>   12 FOR_ITER                12 (to 26)
             14 STORE_FAST               1 (_)

 10          16 LOAD_FAST                0 (y)
             18 LOAD_CONST               3 (1.5)
             20 BINARY_MULTIPLY
             22 STORE_FAST               0 (y)
             24 JUMP_ABSOLUTE           12

 12     >>   26 LOAD_FAST                0 (y)
             28 RETURN_VALUE
  • 字节码含义🤡
  1. 第一列
    第一列是行号,可以和源码对照起来
  2. 第二列
    “>>” 此行是要进行跳跃的行, 比如循环.
    紧接着的数字是字节偏移量你会发现是0、2、4、6等等(也许你会有疑问)
    指令的名字(操作码operation code)
  3. 第三列
    第二列操作码对应的参数和实际的计算值

字节码分析详解👽

如下代码显示了example对象的字节码和__code__对象的部分属性

#!/usr/bin/python
# -*- coding:utf-8 -*-
import dis


def example():
    y = 1.0
    for _ in range(20):
        y = y * 1.5

    return y


def code_object_info(obj):
    print('*' * 30)
    for item in dir(obj):
        if item.startswith("__"):
            continue
        print('{}: {}'.format(item, getattr(obj, item)))
    print('*' * 30)

if __name__ == '__main__':
    code_object_info(example.__code__)
    dis.dis(example)
  • example.__code__的部分属性信息👽
co_argcount: 0
co_cellvars: ()
co_code: b'd\x01}\x00t\x00d\x02\x83\x01D\x00]\x0c}\x01|\x00d\x03\x14\x00}\x00q\x0c|\x00S\x00'
co_consts: (None, 1.0, 20, 1.5)
co_filename: D:\coding\PySource\simpledemo\disdemo2.py
co_firstlineno: 6
co_flags: 67
co_freevars: ()
co_kwonlyargcount: 0
co_lnotab: b'\x00\x01\x04\x01\x0c\x01\n\x02'
co_name: example
co_names: ('range',)
co_nlocals: 2
co_posonlyargcount: 0
co_stacksize: 3
co_varnames: ('y', '_')
replace: <built-in method replace of code object at 0x0000019677901190>
  • example对应的字节码👽
第七行就是example函数的第一行, 这行的是将常量1.0赋值给y
LOAD_CONST指令是偏移量是0, 参数是1, 它将co_consts[1]也就是1.0 push到共享栈栈顶
  7           0 LOAD_CONST               1 (1.0)
STORE_FAST指令偏移量是2, 参数是0, 它将栈顶元素1.0赋值给co_varnames[0]也就是y
              2 STORE_FAST               0 (y)
第八行是个for循环
LOADGLOBAL将co_names[0]也就是range压入到共享栈
  8           4 LOAD_GLOBAL              0 (range)
LOAD_CONST将co_consts[2]也就是20放到共享栈中
              6 LOAD_CONST               2 (20)
CALL_FUNCTION会把栈中的东西全部弹出来, 只支持位置参数, 这个20弹出来和range弹出来,将20传入range函数中,
然后将range(20)放入栈顶
              8 CALL_FUNCTION            1
GET_ITER指令会将栈顶弹出然后返回iter(range(20))
             10 GET_ITER
此时栈顶是个iterator object, 将会逐个调用迭代器的__next__方法获取值
        >>   12 FOR_ITER                12 (to 26)
这个for循环会将迭代器生成的0-19赋值给cor_varnames[1]也就是_
             14 STORE_FAST               1 (_)
LOAD_FAST将cor_varnames[0]也就是y(第7行y已经是1.0了)放入栈顶
  9          16 LOAD_FAST                0 (y)
LOAD_CONST将co_consts[3]也就是1.5放到共享栈中
             18 LOAD_CONST               3 (1.5)
BINARY_MULTIPLY将栈顶1.5和y弹出计算1.5y然后放入栈顶,此时计算结果是1.5
             20 BINARY_MULTIPLY
STORE_FAST将栈顶计算结果1.5赋值给co_varnames[0]也就是y
             22 STORE_FAST               0 (y)
JUMP_ABSOLUTE将字节码计数器又跳转到12行的for循环, 12行有>>,这个就表示jump target 
             24 JUMP_ABSOLUTE           12
LOAD_FAST最后会将co_varnames[0]也就是y放入栈顶,此时栈顶只有y, 也就是最终的结果
 11     >>   26 LOAD_FAST                0 (y)
RETURN_VALUE将栈顶的最终计算结果返回给example函数的调用者
             28 RETURN_VALUE

方法论

我们需要从一个知识挖掘更多的知识,更全面的用关联的思维来学习知识和分析问题,因为这个世界是如此复杂但万事万物又是如此具有普遍联系.

从上面的分析中我们可以看到,解析字节码的过程更像是一个逆波兰表达式子, Reverse Polish notation

逆波兰表达式的解释器一般是基于堆栈的, 不需要区分运算符的优先级,所以不需要查表或者使用括号.

解释过程一般是:操作数入栈;遇到操作符时,操作数出栈,求值,将结果入栈;当一遍后,栈顶就是表达式的值。因此逆波兰表达式的求值使用堆栈结构很容易实现,并且能很快求值。

结束语👽

如果有什么不足的地方,请在下方留言
我是江湖狗哥,我们下期再见

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2022-03-11 22:09:03  更:2022-03-11 22:11:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 21:31:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码