Python バイトコードは主に 2 つの部分で構成され、1 つはオペレーション コード、もう 1 つはオペレーション コードのパラメータです。cpython では一部のバイトコードのみがパラメータを持ちます。 bytecode にはパラメータがないため、oparg の値は 0 に等しくなります。cpython では、opcode
opcode と oparg はそれぞれ 1 バイトを占有し、cpython 仮想マシンはバイトコードを保存するためにリトル エンディアン モードを使用します。
最初にバイトコードの設計を理解するために、次のコード スニペットを使用します:
import dis def add(a, b): return a + b if __name__ == '__main__': print(add.__code__.co_code) print("bytecode: ", list(bytearray(add.__code__.co_code))) dis.dis(add)
Python3.9 での上記のコードの出力は次のとおりです:
b'|\x00|\x01\x17\x00S\x00' bytecode: [124, 0, 124, 1, 23, 0, 83, 0] 5 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4 BINARY_ADD 6 RETURN_VALUE
最初に必要なもの私が理解しているのは、add.__code__.co_code は関数 add のバイトコードであり、バイト シーケンスです。list(bytearray(add.__code__.co_code))
はこのシーケンスをバイトごとに結合することです。それを分割して 10 進数に変換します。先ほど説明した各命令によると、バイトコードは 2 バイトを占めるため、上記のバイトコードには 4 つの命令があります:
オペコードと対応する操作 命令の最後には詳細な対応表があります。記事。上記のコードでは、主に 124、23、83 の 3 つのバイトコード命令が使用されています。それらに対応する演算命令は、それぞれ LOAD_FAST、BINARY_ADD、RETURN_VALUE です。それらの意味は次のとおりです:
LOAD_FAST: varnames[var_num] をスタックの最上位にプッシュします。 BINARY_ADD: スタックから 2 つのオブジェクトをポップし、それらの加算結果をスタックの先頭にプッシュします。 RETURN_VALUE: スタックの先頭にある要素をポップし、関数の戻り値として使用します。
最初に知っておく必要があるのは、BINARY_ADD と RETURN_VALUE です。これら 2 つの演算命令にはパラメータがないため、これら 2 つのオペコードの後のパラメータはすべて 0 です。
しかし、LOAD_FAST にはパラメータがあります。LOAD_FAST が co-varnames[var_num] をスタックにプッシュすることはすでにわかっていますが、var_num は命令 LOAD_FAST のパラメータです。上記のコードには、a と b をスタックにプッシュする 2 つの LOAD_FAST 命令があります。varname の添字はそれぞれ 0 と 1 なので、オペランドは 0 と 1 になります。
上で説明した Python バイトコード オペランドとオペコードはそれぞれ 1 バイトを占有しますが、varname または定数テーブル データの数が 1 バイトより大きい場合、表現範囲が 1 バイトである場合、どうやって対処すればいいのでしょうか?
この問題を解決するために、cpython はバイトコードの拡張パラメーターを設計します。たとえば、定数テーブルに添字 66113 を持つオブジェクトをロードする場合、対応するバイトコードは次のようになります:
[144, 1, 144, 2, 100, 65]
144 は EXTENDED_ARG を表しますが、これは本質的に Python 仮想マシンによって実行される必要があるバイトコードではありません。このフィールドは主に拡張パラメーターの計算のために設計されています。
100 に対応する演算命令は LOAD_CONST で、その演算コードは 65 です。ただし、上記の命令では、定数テーブルの添字 65 を持つオブジェクトはロードされませんが、添字 66113 を持つオブジェクトがロードされます。理由は EXTENDED_ARG のためです。
ここで、上記の分析プロセスをシミュレートしましょう:
最初にバイトコード命令を読み取ります。オペコードは 144 に等しく、これは拡張パラメータであることを示します。次に、この時点のパラメータ arg は等しいです。から (1 x (1
上記の計算処理をプログラムコードで表すと以下のようになります. 以下のコードでは、コードは実バイト列 HAVE_ARGUMENT = 90 になります。
def _unpack_opargs(code): extended_arg = 0 for i in range(0, len(code), 2): op = code[i] if op >= HAVE_ARGUMENT: arg = code[i+1] | extended_arg extended_arg = (arg << 8) if op == EXTENDED_ARG else 0 else: arg = None yield (i, op, arg)
コードを使用して以前の分析を検証できます:
import dis def num_to_byte(n): return n.to_bytes(1, "little") def nums_to_bytes(data): ans = b"".join([num_to_byte(n) for n in data]) return ans if __name__ == '__main__': # extended_arg extended_num opcode oparg for python_version > 3.5 bytecode = nums_to_bytes([144, 1, 144, 2, 100, 65]) print(bytecode) dis.dis(bytecode)
上記のコードの出力結果は次のとおりです:
b'\x90\x01\x90\x02dA' 0 EXTENDED_ARG 1 2 EXTENDED_ARG 258 4 LOAD_CONST 66113 (66113)
出力結果に従って確認できます。上記のプログラムの分析は正しいことが判明しました。
ソース コード バイトコード マッピング テーブル
このセクションでは、主にコード オブジェクト オブジェクト内の co_lnotab フィールドを分析し、特定のフィールドを分析することでこのフィールドの設計を学習します。
import dis def add(a, b): a += 1 b += 2 return a + b if __name__ == '__main__': dis.dis(add.__code__) print(f"{list(bytearray(add.__code__.co_lnotab)) = }") print(f"{add.__code__.co_firstlineno = }")
まず、dis の出力の 1 列目はバイトコードに対応するソース コードの行番号、2 列目はバイト シーケンス内のバイトコードのディスプレースメントです。
上記のコードの出力結果は次のとおりです。
源代码的行号 字节码的位移 6 0 LOAD_FAST 0 (a) 2 LOAD_CONST 1 (1) 4 INPLACE_ADD 6 STORE_FAST 0 (a) 7 8 LOAD_FAST 1 (b) 10 LOAD_CONST 2 (2) 12 INPLACE_ADD 14 STORE_FAST 1 (b) 8 16 LOAD_FAST 0 (a) 18 LOAD_FAST 1 (b) 20 BINARY_ADD 22 RETURN_VALUE list(bytearray(add.__code__.co_lnotab)) = [0, 1, 8, 1, 8, 1] add.__code__.co_firstlineno = 5
上記のコードの出力結果から、バイトコードが 3 つのセグメントに分割されており、各セグメントがバイトコードを表していることがわかります。 1 行のコードのバイトコード。次に、co_lnotab フィールドを分析してみましょう。このフィールドは実際には 2 バイトに分割されています。たとえば、上記の [0, 1, 8, 1, 8, 1] は、[0, 1]、[8, 1]、[8, 1] の 3 つのセグメントに分割できます。意味は次のとおりです:
第一个数字表示距离上一行代码的字节码数目。 第二个数字表示距离上一行有效代码的行数。
现在我们来模拟上面代码的字节码的位移和源代码行数之间的关系:
[0, 1],说明这行代码离上一行代码的字节位移是 0 ,因此我们可以看到使用 dis 输出的字节码 LOAD_FAST ,前面的数字是 0,距离上一行代码的行数等于 1 ,代码的第一行的行号等于 5,因此 LOAD_FAST 对应的行号等于 5 + 1 = 6 。 [8, 1],说明这行代码距离上一行代码的字节位移为 8 个字节,因此第二块的 LOAD_FAST 前面是 8 ,距离上一行代码的行数等于 1,因此这个字节码对应的源代码的行号等于 6 + 1 = 7。 [8, 1],同理可以知道这块字节码对应源代码的行号是 8 。
现在有一个问题是当两行代码之间相距的行数超过 一个字节的表示范围怎么办?在 python3.5 以后如果行数差距大于 127,那么就使用 (0, 行数) 对下一个组合进行表示,(0, \(x_1\)), (0,$ x_2$) ... ,直到 \(x_1 + ... + x_n\) = 行数。
在后面的程序当中我们会使用 compile 这个 python 内嵌函数。当你使用Python编写代码时,可以使用compile()
函数将Python代码编译成字节代码对象。这个字节码对象可以被传递给Python的解释器或虚拟机,以执行代码。
compile()
函数接受三个参数:
source
: 要编译的Python代码,可以是字符串,字节码或AST对象。 filename
: 代码来源的文件名(如果有),通常为字符串。 mode
: 编译代码的模式。可以是 'exec'、'eval' 或 'single' 中的一个。'exec' 模式用于编译多行代码,'eval' 用于编译单个表达式,'single' 用于编译单行代码。
import dis code = """ x=1 y=2 """ \ + "\n" * 500 + \ """ z=x+y """ code = compile(code, '<string>', 'exec') print(list(bytearray(code.co_lnotab))) print(code.co_firstlineno) dis.dis(code)
上面的代码输出结果如下所示:
[0, 1, 4, 1, 4, 127, 0, 127, 0, 127, 0, 121] 1 2 0 LOAD_CONST 0 (1) 2 STORE_NAME 0 (x) 3 4 LOAD_CONST 1 (2) 6 STORE_NAME 1 (y) 505 8 LOAD_NAME 0 (x) 10 LOAD_NAME 1 (y) 12 BINARY_ADD 14 STORE_NAME 2 (z) 16 LOAD_CONST 2 (None) 18 RETURN_VALUE
根据我们前面的分析因为第三行和第二行之间的差距大于 127 ,因此后面的多个组合都是用于表示行数的。
505 = 3(前面已经有三行了) + (127 + 127 + 127 + 121)(这个是第二行和第三行之间的差距,这个值为 502,中间有 500 个换行但是因为字符串相加的原因还增加了两个换行,因此一共是 502 个换行)。
具体的算法用代码表示如下所示,下面的参数就是我们传递给 dis 模块的 code,也就是一个 code object 对象。
def findlinestarts(code): """Find the offsets in a byte code which are start of lines in the source. Generate pairs (offset, lineno) as described in Python/compile.c. """ byte_increments = code.co_lnotab[0::2] line_increments = code.co_lnotab[1::2] bytecode_len = len(code.co_code) lastlineno = None lineno = code.co_firstlineno addr = 0 for byte_incr, line_incr in zip(byte_increments, line_increments): if byte_incr: if lineno != lastlineno: yield (addr, lineno) lastlineno = lineno addr += byte_incr if addr >= bytecode_len: # The rest of the lnotab byte offsets are past the end of # the bytecode, so the lines were optimized away. return if line_incr >= 0x80: # line_increments is an array of 8-bit signed integers line_incr -= 0x100 lineno += line_incr if lineno != lastlineno: yield (addr, lineno)
操作 | 操作码 |
---|---|
POP_TOP | 1 |
ROT_TWO | 2 |
ROT_THREE | 3 |
DUP_TOP | 4 |
DUP_TOP_TWO | 5 |
ROT_FOUR | 6 |
NOP | 9 |
UNARY_POSITIVE | 10 |
UNARY_NEGATIVE | 11 |
UNARY_NOT | 12 |
UNARY_INVERT | 15 |
BINARY_MATRIX_MULTIPLY | 16 |
INPLACE_MATRIX_MULTIPLY | 17 |
BINARY_POWER | 19 |
BINARY_MULTIPLY | 20 |
BINARY_MODULO | 22 |
BINARY_ADD | 23 |
BINARY_SUBTRACT | 24 |
BINARY_SUBSCR | 25 |
BINARY_FLOOR_DIVIDE | 26 |
BINARY_TRUE_DIVIDE | 27 |
INPLACE_FLOOR_DIVIDE | 28 |
INPLACE_TRUE_DIVIDE | 29 |
RERAISE | 48 |
WITH_EXCEPT_START | 49 |
GET_AITER | 50 |
GET_ANEXT | 51 |
BEFORE_ASYNC_WITH | 52 |
END_ASYNC_FOR | 54 |
INPLACE_ADD | 55 |
INPLACE_SUBTRACT | 56 |
INPLACE_MULTIPLY | 57 |
INPLACE_MODULO | 59 |
STORE_SUBSCR | 60 |
DELETE_SUBSCR | 61 |
BINARY_LSHIFT | 62 |
BINARY_RSHIFT | 63 |
BINARY_AND | 64 |
BINARY_XOR | 65 |
BINARY_OR | 66 |
INPLACE_POWER | 67 |
GET_ITER | 68 |
GET_YIELD_FROM_ITER | 69 |
PRINT_EXPR | 70 |
LOAD_BUILD_CLASS | 71 |
YIELD_FROM | 72 |
GET_AWAITABLE | 73 |
LOAD_ASSERTION_ERROR | 74 |
INPLACE_LSHIFT | 75 |
INPLACE_RSHIFT | 76 |
INPLACE_AND | 77 |
INPLACE_XOR | 78 |
INPLACE_OR | 79 |
LIST_TO_TUPLE | 82 |
RETURN_VALUE | 83 |
IMPORT_STAR | 84 |
SETUP_ANNOTATIONS | 85 |
YIELD_VALUE | 86 |
POP_BLOCK | 87 |
POP_EXCEPT | 89 |
STORE_NAME | 90 |
DELETE_NAME | 91 |
UNPACK_SEQUENCE | 92 |
FOR_ITER | 93 |
UNPACK_EX | 94 |
STORE_ATTR | 95 |
DELETE_ATTR | 96 |
STORE_GLOBAL | 97 |
DELETE_GLOBAL | 98 |
LOAD_CONST | 100 |
LOAD_NAME | 101 |
BUILD_TUPLE | 102 |
BUILD_LIST | 103 |
BUILD_SET | 104 |
BUILD_MAP | 105 |
LOAD_ATTR | 106 |
COMPARE_OP | 107 |
IMPORT_NAME | 108 |
IMPORT_FROM | 109 |
JUMP_FORWARD | 110 |
JUMP_IF_FALSE_OR_POP | 111 |
JUMP_IF_TRUE_OR_POP | 112 |
JUMP_ABSOLUTE | 113 |
POP_JUMP_IF_FALSE | 114 |
POP_JUMP_IF_TRUE | 115 |
LOAD_GLOBAL | 116 |
IS_OP | 117 |
CONTAINS_OP | 118 |
JUMP_IF_NOT_EXC_MATCH | 121 |
SETUP_FINALLY | 122 |
LOAD_FAST | 124 |
STORE_FAST | 125 |
DELETE_FAST | 126 |
RAISE_VARARGS | 130 |
CALL_FUNCTION | 131 |
MAKE_FUNCTION | 132 |
BUILD_SLICE | 133 |
LOAD_CLOSURE | 135 |
LOAD_DEREF | 136 |
STORE_DEREF | 137 |
DELETE_DEREF | 138 |
CALL_FUNCTION_KW | 141 |
CALL_FUNCTION_EX | 142 |
SETUP_WITH | 143 |
LIST_APPEND | 145 |
SET_ADD | 146 |
MAP_ADD | 147 |
LOAD_CLASSDEREF | 148 |
EXTENDED_ARG | 144 |
SETUP_ASYNC_WITH | 154 |
FORMAT_VALUE | 155 |
BUILD_CONST_KEY_MAP | 156 |
BUILD_STRING | 157 |
LOAD_METHOD | 160 |
CALL_METHOD | 161 |
LIST_EXTEND | 162 |
SET_UPDATE | 163 |
DICT_MERGE | 164 |
DICT_UPDATE | 165 |
以上がPython仮想マシンの使い方の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。