Подтвердить что ты не робот

Процесс компиляции/интерпретации Python

Я пытаюсь понять процесс компилятора/интерпретатора python более четко. К сожалению, я не учился в устных переводчиках и не читал много о них.

В принципе, сейчас я понимаю, что код Python из .py файлов сначала скомпилирован в байт-код python (который я предполагаю, это файлы .pyc, которые я иногда вижу?). Затем байт-код компилируется в машинный код, язык, который действительно понимает процессор. В основном, я прочитал эту тему Почему python компилирует исходный код в байт-код перед интерпретацией?

Может ли кто-нибудь дать мне хорошее объяснение всему процессу, имея в виду, что мои знания о компиляторах/переводчиках почти не существуют? Или, если это невозможно, возможно, дайте мне некоторые ресурсы, которые дают краткий обзор компиляторов/переводчиков?

Спасибо

4b9b3361

Ответ 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 в стек.

Ответ 2

Чтобы завершить большой ответ Марсело Канто, вот только небольшая сводка за столбцом, чтобы объяснить вывод дизассемблированного байт-кода.

Например, с учетом этой функции:

def f(num):
    if num == 42:
        return True
    return False

Это может быть разобрано в (Python 3.6):

(1)|(2)|(3)|(4)|          (5)         |(6)|  (7)
---|---|---|---|----------------------|---|-------
  2|   |   |  0|LOAD_FAST             |  0|(num)
   |-->|   |  2|LOAD_CONST            |  1|(42)
   |   |   |  4|COMPARE_OP            |  2|(==)
   |   |   |  6|POP_JUMP_IF_FALSE     | 12|
   |   |   |   |                      |   |
  3|   |   |  8|LOAD_CONST            |  2|(True)
   |   |   | 10|RETURN_VALUE          |   |
   |   |   |   |                      |   |
  4|   |>> | 12|LOAD_CONST            |  3|(False)
   |   |   | 14|RETURN_VALUE          |   |

Каждый столбец имеет определенную цель:

  • Соответствующий номер строки в исходном коде
  • Необязательно указывает на выполнение текущей инструкции (когда байт-код поступает из фрейм-объекта)
  • Метка, которая обозначает возможную JUMP от более ранней команды к этому
  • адрес в байтовом коде, который соответствует индексу байта (они кратные 2, потому что Python 3.6 использует 2 байта для каждой команды, в то время как он может меняться в предыдущих версиях)
  • Имя команды (также называемое opname), каждый из которых кратко поясняется в dis module и их реализация может быть найдена в ceval.c (основной цикл CPython)
  • аргумент (если есть) инструкции, которая используется внутри Python для извлечения некоторых констант или переменных, управления стеком, перехода к определенной команде и т.д.
  • дружественная для человека интерпретация аргумента команды