Ответ 1
Байт-код на самом деле не интерпретируется машинным кодом, если вы не используете какую-либо экзотическую реализацию, такую как pypy.
Кроме этого, у вас есть правильное описание. Байт-код загружается в среду исполнения Python и интерпретируется виртуальной машиной, которая является частью кода, который считывает каждую инструкцию в байт-коде и выполняет любую операцию. Вы можете увидеть этот байт-код с модулем dis
следующим образом:
>>> def fib(n): return n if n < 2 else fib(n - 2) + fib(n - 1)
...
>>> fib(10)
55
>>> import dis
>>> dis.dis(fib)
1 0 LOAD_FAST 0 (n)
3 LOAD_CONST 1 (2)
6 COMPARE_OP 0 (<)
9 JUMP_IF_FALSE 5 (to 17)
12 POP_TOP
13 LOAD_FAST 0 (n)
16 RETURN_VALUE
>> 17 POP_TOP
18 LOAD_GLOBAL 0 (fib)
21 LOAD_FAST 0 (n)
24 LOAD_CONST 1 (2)
27 BINARY_SUBTRACT
28 CALL_FUNCTION 1
31 LOAD_GLOBAL 0 (fib)
34 LOAD_FAST 0 (n)
37 LOAD_CONST 2 (1)
40 BINARY_SUBTRACT
41 CALL_FUNCTION 1
44 BINARY_ADD
45 RETURN_VALUE
>>>
Подробное объяснение
Очень важно понять, что вышеуказанный код никогда не выполняется вашим CPU; и он никогда не превращается во что-то, что (по крайней мере, не на официальную реализацию C на Python). ЦП выполняет код виртуальной машины, который выполняет работу, указанную инструкциями байт-кода. Когда интерпретатор хочет выполнить функцию fib
, он читает инструкции по одному и делает то, что они говорят ему. Он смотрит на первую команду, LOAD_FAST 0
и, таким образом, захватывает параметр 0 (n
, переданный в fib
), где бы ни находились параметры и помещал его в стек интерпретатора (интерпретатор Python - это стековый компьютер). При чтении следующей команды LOAD_CONST 1
она захватывает постоянное число 1 из набора констант, принадлежащих этой функции, которая в этом случае оказывается номером 2 и толкает ее в стек. Вы можете увидеть эти константы:
>>> fib.func_code.co_consts
(None, 2, 1)
Следующая инструкция COMPARE_OP 0
сообщает интерпретатору о том, чтобы вытащить два самых верхних элемента стека и выполнить сравнение неравенства между ними, оттесняя результат Boolean обратно в стек. Четвертая команда определяет на основе логического значения, следует ли перепрыгнуть пять инструкций или продолжить следующую команду. Вся эта формулировка объясняет if n < 2
часть условного выражения в fib
. Это будет очень поучительное упражнение для вас, чтобы разделить смысл и поведение остальных байт-кодов fib
. Единственный, я не уверен, что это POP_TOP
; Я предполагаю, что JUMP_IF_FALSE
определяется, чтобы оставить свой логический аргумент в стеке, а не выскакивать его, поэтому его нужно явно вывести.
Еще более поучительно проверить исходный байт-код для fib
таким образом:
>>> code = fib.func_code.co_code
>>> code
'|\x00\x00d\x01\x00j\x00\x00o\x05\x00\x01|\x00\x00S\x01t\x00\x00|\x00\x00d\x01\x00\x18\x83\x01\x00t\x00\x00|\x00\x00d\x02\x00\x18\x83\x01\x00\x17S'
>>> import opcode
>>> op = code[0]
>>> op
'|'
>>> op = ord(op)
>>> op
124
>>> opcode.opname[op]
'LOAD_FAST'
>>>
Таким образом, вы можете видеть, что первый байт байт-кода является инструкцией LOAD_FAST
. Следующая пара байтов '\x00\x00'
(число 0 в 16 бит) является аргументом LOAD_FAST
и сообщает интерпретатору байт-кода загрузить параметр 0 в стек.