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

Как точно работает Python Bytecode в CPython?

Я пытаюсь понять, как работает Python (потому что я использую его все время!). Насколько я понимаю, когда вы запускаете что-то вроде python script.py, script преобразуется в байт-код, а затем интерпретатор /VM/CPython - на самом деле просто программа C-читает в байт-коде python и выполняет соответствующую программу.

Как читается этот байт-код? Это похоже на то, как текстовый файл читается в C? Я не уверен, как код Python преобразуется в машинный код. Является ли это тот факт, что интерпретатор Python (команда python в CLI) на самом деле представляет собой только предварительно скомпилированную C-программу, которая уже преобразована в машинный код, а затем файлы байт-кода python просто проходят через эту программу? Другими словами, моя программа Python никогда не была преобразована в машинный код? Является ли интерпретатор python уже машинным кодом, поэтому мой script никогда не должен быть?

4b9b3361

Ответ 1

Да, ваше понимание верное. В интерпретаторе CPython существует принципиально (в основном) гигантский оператор switch, в котором говорится: "Если текущий код операции таков и так, сделайте это и что".

http://hg.python.org/cpython/file/3.3/Python/ceval.c#l790

Другие реализации, такие как Pypy, имеют JIT-компиляцию, т.е. перевод Python на машинные коды на лету.

Ответ 2

Если вы хотите увидеть байт-код какого-либо кода (будь то исходный код, объект живой функции или объект кода и т.д.), модуль dis будет скажите вам, что вам нужно. Например:

>>> dis.dis('i/3')
  1           0 LOAD_NAME                0 (i)
              3 LOAD_CONST               0 (3)
              6 BINARY_TRUE_DIVIDE
              7 RETURN_VALUE

Документы dis объясняют, что означает каждый байт-код. Например, LOAD_NAME:

Вставляет значение, связанное с co_names[namei], в стек.

Чтобы понять это, вы должны знать, что интерпретатор байт-кода является виртуальной машиной и что co_names. В документах inspect есть хорошая таблица, показывающая наиболее важные атрибуты наиболее важных внутренних объектов, поэтому вы можете видеть, что co_names является атрибутом code объекты, которые содержат кортеж имен локальных переменных. Другими словами, LOAD_NAME 0 выталкивает значение, связанное с 0-й локальной переменной (и dis помогает это посмотреть и видит, что 0-я локальная переменная называется 'i').

И этого достаточно, чтобы убедиться, что строки байт-кодов недостаточно; интерпретатору также нужны другие атрибуты объекта кода, а в некоторых случаях - атрибуты объекта функции (который также встречается в среде локальных и глобальных переменных).

В модуле inspect также есть некоторые инструменты, которые могут помочь вам в исследовании кода в реальном времени.

Этого достаточно, чтобы выяснить много интересного. Например, вы, вероятно, знаете, что Python во время компиляции определяет, является ли переменная в функции локальной, закрывающей или глобальной, на основе того, назначается ли она ей в любом месте тела функции (и на любом nonlocal или global заявления); если вы пишете три разные функции и сравниваете их разборку (и соответствующие другие атрибуты), вы можете легко определить, что именно должно делать.

(Один бит, который сложнее здесь, заключается в понимании замыкающих ячеек. Чтобы действительно получить это, вам нужно будет иметь 3 уровня функций, чтобы увидеть, как один из них перемещается вперед для самого внутреннего.)


Чтобы понять, как интерпретируется байт-код и как работает стековый компьютер (в CPython), вам нужно посмотреть исходный код ceval.c. Ответы thy435 и eyquem уже охватывают это.


Понимание того, как файлы pyc читаются, требует немного больше информации. У Ned Batchelder есть отличная (если и немного устаревшая) запись в блоге, называемая Структура файлов .pyc, которая охватывает все сложные и не-хорошо документированные частей. (Обратите внимание, что в 3.3 некоторые из gory-кода, связанные с импортом, были перемещены из C в Python, что значительно облегчает его выполнение.) Но в основном это просто часть информации заголовка и объекта code, сериализована marshal.


Чтобы понять, как источник скомпилирован в байт-код, эта забавная часть.

Дизайн CPython Compiler объясняет, как все работает. (Некоторые из других разделов Руководство для разработчиков Python также полезны.)

Для раннего токенинга и синтаксического анализа вы можете просто использовать модуль ast, чтобы перейти прямо к точке, где нужно выполнить фактическую компиляцию. Затем см. compile.c о том, как AST превращается в байт-код.

Макросы могут быть немного трудными для работы, но как только вы поймете, как компилятор использует стек для спуска в блоки, и как он использует этих compiler_addop и друзей для испускания байт-кодов на текущем уровне, все это имеет смысл.

Одна вещь, которая сначала удивляет большинство людей, - это то, как работают функции. Тело определения функции скомпилировано в объект кода. Тогда само определение функции компилируется в код (внутри тела, модуля и т.д.), Который при выполнении строит объект функции из этого кодового объекта. (Как только вы думаете о том, как должны работать замыкания, очевидно, почему это работает. Каждый экземпляр замыкания представляет собой отдельный объект функции с тем же кодовым объектом.)


И теперь вы готовы начать исправление CPython, чтобы добавить свои собственные заявления, не так ли? Ну, как Изменение CPython Grammar показывает, что есть много вещей, чтобы получить право (и там даже больше, если вам нужно создать новые коды операций). Возможно, вам будет легче узнать PyPy, а также CPython, и сначала начните взламывать PyPy, а только вернитесь на CPython, как только вы узнаете, что вы делать разумно и выполнимо.

Ответ 3

Прочитав ответ thg4535, я уверен, что вы найдете интересные пояснения к ceval.c: Здравствуйте, ceval.c!

Эта статья является частью серии, написанной Янивом Акнином, чей я фанат: Python Innards