Я пытаюсь понять процесс компилятора/интерпретатора Python более ясно. К сожалению, я не посещал урок в интерпретаторах, и при этом я не читал очень о них.
В основном то, что я понимаю прямо сейчас, - то, что код Python из .py файлов сначала компилируется в байт-код Python (который я принимаю, .pyc файлы, которые я иногда вижу?). Затем, байт-код компилируется в машинный код, язык, который на самом деле понимает процессор. В значительной степени я считал этот поток Почему компиляция Python источник к байт-коду перед интерпретацией?
Кто-то мог дать мне хорошее объяснение целого процесса, имеющего в виду, что мое знание компиляторов/интерпретаторов почти не существует? Или, если это не возможно, возможно, дайте мне некоторые ресурсы, которые дают быстрые обзоры компиляторов/интерпретаторов?
Спасибо
Байт-код на самом деле не интерпретируется в машинный код, если вы не используете какую-то экзотическую реализацию, такую как 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
>>>
Очень важно понимать, что приведенный выше код никогда не выполняется вашим процессором; при этом он никогда не конвертируется во что-то, что есть (по крайней мере, не в официальной реализации Python на языке C). ЦП выполняет код виртуальной машины, который выполняет работу, указанную инструкциями байт-кода. Когда интерпретатор хочет выполнить функцию 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 в стек. .