Rumah > Artikel > pembangunan bahagian belakang > Bagaimana untuk menggunakan mesin maya python
Kod bait python terutamanya terdiri daripada dua bahagian, satu ialah kod operasi, dan satu lagi ialah parameter kod operasi Dalam cpython, hanya beberapa kod bait yang mempunyai parameter bytecode tidak mempunyai parameter, jadi nilai oparg adalah sama dengan 0. Dalam cpython, arahan dengan opcode
opcode dan oparg masing-masing menduduki satu bait, dan mesin maya cpython menggunakan mod endian kecil untuk menyimpan bytecode.
Kami menggunakan coretan kod berikut untuk memahami reka bentuk kod byte dahulu:
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)
Keluaran kod di atas dalam python3.9 adalah seperti berikut:
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
Keperluan pertama Apa yang saya faham ialah add.__code__.co_code ialah bytecode bagi fungsi add, iaitu jujukan bait list(bytearray(add.__code__.co_code))
memisahkan jujukan bait demi bait dan menukarkannya kepada bentuk perpuluhan. Mengikut setiap arahan yang kita bincangkan sebelum ini - bytecode menduduki 2 byte, jadi bytecode di atas mempunyai empat arahan:
opcode dan operasi yang sepadan Arahan mempunyai jadual surat-menyurat terperinci pada penghujung artikel itu. Dalam kod di atas, tiga arahan kod bait digunakan terutamanya, iaitu 124, 23 dan 83. Arahan operasi yang sepadan adalah LOAD_FAST, BINARY_ADD dan RETURN_VALUE masing-masing. Maksudnya adalah seperti berikut:
LOAD_FAST: Tolak varname[var_num] ke bahagian atas tindanan. BINARY_ADD: Pop dua objek dari tindanan dan tolak hasil penambahannya ke bahagian atas tindanan. RETURN_VALUE: Pop elemen di bahagian atas tindanan dan gunakannya sebagai nilai pulangan fungsi.
Perkara pertama yang perlu kita ketahui ialah BINARY_ADD dan RETURN_VALUE kedua-dua arahan operasi ini tidak mempunyai parameter, jadi parameter selepas kedua-dua opcode ini semuanya 0.
Tetapi LOAD_FAST mempunyai parameter Kami sudah tahu di atas bahawa LOAD_FAST menolak co-varnames[var_num] ke dalam tindanan, dan var_num ialah parameter arahan LOAD_FAST. Terdapat dua arahan LOAD_FAST dalam kod di atas, yang menolak a dan b ke dalam timbunan Subskrip mereka dalam nama var.
Operand bytecode python dan opcode yang kita bincangkan di atas setiap bait menduduki satu bait, tetapi jika bilangan vaname atau data jadual tetap lebih besar daripada Jika julat perwakilan ialah 1 bait , bagaimana cara mengatasinya?
Untuk menyelesaikan masalah ini, cpython telah memanjangkan parameter yang direka untuk bytecode Contohnya, jika kita ingin memuatkan objek dengan subskrip 66113 dalam jadual tetap, maka bytecode yang sepadan adalah seperti berikut:
[144, 1, 144, 2, 100, 65]
Antaranya, 144 mewakili EXTENDED_ARG, yang pada asasnya bukan kod bait yang perlu dilaksanakan oleh mesin maya python Medan ini direka terutamanya untuk pengiraan parameter lanjutan. Perintah operasi yang sepadan untuk
100 ialah LOAD_CONST, dan opcodenya ialah 65. Walau bagaimanapun, arahan di atas tidak akan memuatkan objek dengan subskrip 65 dalam jadual tetap, tetapi akan memuatkan objek dengan subskrip 66113. Sebabnya ialah Kerana EXTENDED_ARG.
Sekarang mari kita simulasi proses analisis di atas:
Mula-mula baca arahan bytecode, opcode adalah sama dengan 144, menunjukkan bahawa ia adalah parameter lanjutan, maka parameter arg pada masa ini adalah sama kepada (1 x (1
Proses pengiraan di atas diwakili oleh kod program seperti berikut Dalam kod berikut, kod ialah jujukan bait sebenar 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)
Kami boleh menggunakan kod untuk mengesahkan analisis kami sebelum ini:
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)
Keluaran kod di atas adalah seperti berikut:
b'\x90\x01\x90\x02dA' 0 EXTENDED_ARG 1 2 EXTENDED_ARG 258 4 LOAD_CONST 66113 (66113)
Seperti yang anda lihat daripada output program di atas Analisis kami ternyata betul.
Jadual pemetaan bytecode kod sumber
Dalam bahagian ini, kami terutamanya menganalisis medan co_lnotab dalam objek objek kod dan mempelajari reka bentuk medan ini dengan menganalisis medan tertentu.
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 = }")
Pertama sekali, lajur pertama keluaran dis ialah nombor baris kod sumber yang sepadan dengan kod bait, dan lajur kedua ialah anjakan kod bait dalam jujukan bait.
Hasil keluaran kod di atas adalah seperti berikut:
源代码的行号 字节码的位移 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
Daripada hasil keluaran kod di atas, kita dapat lihat kod bait dibahagikan kepada tiga segmen, setiap segmen mewakili kod bait daripada satu baris kod. Sekarang mari kita menganalisis medan co_lnotab Medan ini sebenarnya dibahagikan kepada dua bait. Sebagai contoh, [0, 1, 8, 1, 8, 1] di atas boleh dibahagikan kepada tiga segmen [0, 1], [8, 1], [8, 1]. Maksudnya ialah:
第一个数字表示距离上一行代码的字节码数目。 第二个数字表示距离上一行有效代码的行数。
现在我们来模拟上面代码的字节码的位移和源代码行数之间的关系:
[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 |
Atas ialah kandungan terperinci Bagaimana untuk menggunakan mesin maya python. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!