Байт-код фактически не интерпретируется для машинного кода, если вы не используете какую-то экзотическую реализацию, такую как 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
>>>
Подробное объяснение
Очень важно понимать, что приведенный выше код никогда не выполняется вашим процессором; и при этом это никогда не преобразовывается во что-то, что является (по крайней мере, не на официальной реализации 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
, говорит интерпретатору вытолкнуть два верхних элемента стека и выполнить сравнение неравенства между ними, перенеся логический результат обратно в стек. Четвертая инструкция на основе логического значения определяет, следует ли перейти на пять инструкций вперед или перейти к следующей инструкции. Все эти слова объясняют 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 в стек.