简介👹
非常高兴大家能够订阅这个专栏,在这里我将会给大家分享一些Python相关源码的剖析 在接下来的这段日子里,我会一同带各位pythonista探索Python的奥秘
Pyc文件🐶
当在文件被当成模块导入时才会生成pyc文件, pyc文件是Python的字节码文件, Python文件在经过解释器解释后生成字节码对象PyCodeObject,然后持久化到pyc中
当多次运行程序时,不需要重新对导入的模块进行重新解释(检查pyc没有过期,即字节码文件的修改时间是否与脚本一致), 可以加快程序的运行
pyc文件都被放到__pycache__文件夹下, pyc文件名格式是文件名.python解释器类型-解释器版本, 特比说明的是可以用pyc文件替换py文件,它们功能上是相同的
pyc文件格式如: classdemo.cpython-39.pyc
- python -B filename.py
- 环境PYTHONDONTWRITECODE变量设置成非空字符串
- 以上两种方法等价
你通常不需要关注__pycache__,在一些版本控制中如git,一般忽略提交__pycache__文件, 可以在.gitignore文件中加入**__pycache__来忽略
dis module and opcode 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个字节, 之前版本占用字节数是根据版本变化的
#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>
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(字节码分析)🤡
如何查看字节码🤡
import dis
from types import CodeType
def example():
y = 1.0
for _ in range(20):
y = y * 1.5
return y
if __name__ == '__main__':
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
- 第一列
第一列是行号,可以和源码对照起来 - 第二列
“>>” 此行是要进行跳跃的行, 比如循环. 紧接着的数字是字节偏移量你会发现是0、2、4、6等等(也许你会有疑问) 指令的名字(操作码operation code) - 第三列
第二列操作码对应的参数和实际的计算值
字节码分析详解👽
如下代码显示了example对象的字节码和__code__对象的部分属性
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)
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函数的第一行, 这行的是将常量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
逆波兰表达式的解释器一般是基于堆栈的, 不需要区分运算符的优先级,所以不需要查表或者使用括号.
解释过程一般是:操作数入栈;遇到操作符时,操作数出栈,求值,将结果入栈;当一遍后,栈顶就是表达式的值。因此逆波兰表达式的求值使用堆栈结构很容易实现,并且能很快求值。
结束语👽
如果有什么不足的地方,请在下方留言 我是江湖狗哥,我们下期再见
|