Интерпретатор Python

Из статьи вы узнаете, как работает интерпретатор Python, зачем ему компилятор и байт-код, что такое каталог pycache, какие бывают реализации (CPython, PyPy и другие) и что появилось в новых версиях (JIT и отключаемый GIL).

Компилируемые и интерпретируемые языки программирования

Языки программирования высокого уровня ближе к человеческому языку, чем к машинному. А процессор понимает только машинный код. Поэтому программу, написанную на таком языке, нужно перевести в машинный код. Такими переводчиками стали компиляторы и интерпретаторы. Исторически все высокоуровневые языки делят на компилируемые и интерпретируемые.

Если язык компилируемый, например C, то написав код программы, вы вначале компилируете его, то есть целиком переводите в машинный код. В результате получается бинарный файл, который можно выполнять. Такие файлы платформозависимы: для другой операционной системы или другого типа процессора код нужно компилировать заново. Зато работают они очень быстро, ведь в них уже записан машинный код, который процессор выполняет напрямую.

Если язык интерпретируемый, например Python, то программа это просто текстовый файл с кодом, а выполняет его другая программа — интерпретатор. Поэтому написанный код будет работать в любой операционной системе и на любом процессоре, главное чтобы там был установлен интерпретатор этого языка. Это называется кроссплатформенность. Но выполнение такой программы медленнее, ведь интерпретатор сам тратит время на разбор и выполнение кода.

Важное уточнение: такое деление условное. Правильнее говорить не о языке, а о его реализации. Для одного языка могут существовать и интерпретаторы, и компиляторы. А в «интерпретируемом» Python, как мы сейчас увидим, компилятор тоже есть.

Интерпретатор Python

Как вы уже поняли, обычно Python называют интерпретируемым языком. Это означает, что для запуска программы на Python в системе должен быть установлен интерпретатор Python. В большинстве дистрибутивов Linux он уже есть, проверить версию можно так:

$ python3 --version
Python 3.13.16

Код выполняет не процессор, а именно интерпретатор. Чтобы запустить программу, вы обычно выполняете такую команду:

$ python3 program.py

То есть вы просите интерпретатор (python3) выполнить код, написанный в файле (program.py). Интерпретатор читает файл, выполняет его и выдаёт нам результат.

Но давайте поближе познакомимся с интерпретатором Python, ведь он не так прост, как кажется.

Распространённое заблуждение: «интерпретатор берёт строку кода, переводит её в машинный код, выполняет, потом берёт следующую». На самом деле всё устроено сложнее. Внутри интерпретатора есть компилятор, который переводит код на Python не в машинный код, а в байт-код. И есть виртуальная машина, которая этот байт-код выполняет.

Байт-код это набор простых инструкций для виртуальной машины Python. Это не машинный код, поэтому напрямую процессор его не выполняет. Но он гораздо проще исходного кода, а значит интерпретатору его выполнять быстрее. Кроме того, байт-код не зависит от операционной системы и типа процессора.

Виртуальная машина, которая выполняет байт-код, встроена в интерпретатор. Её называют Python VM или PVM. По сути, это большой цикл, который берёт инструкции байт-кода по одной и выполняет их.

Теперь схема становится более интересной, ведь мы знаем, что внутри интерпретатора работают компилятор и виртуальная машина:

  1. Мы просим интерпретатор (python3) выполнить программу (program.py).
  2. Интерпретатор читает файл и отдаёт код компилятору.
  3. Компилятор создаёт байт-код.
  4. Виртуальная машина (PVM) выполняет этот байт-код.
  5. В конце мы получаем результат.

Интерпретатор, который устроен по такой схеме и написан на языке C, называется CPython. Это основная, или как говорят, эталонная реализация Python. Именно её вы скачиваете с python.org и ставите из пакетов дистрибутива. Когда в статьях пишут просто «Python», почти всегда имеют в виду CPython.

Смотрим байт-код своими глазами

Байт-код можно посмотреть. Для этого в стандартной библиотеке есть модуль dis (от англ. disassembler). Возьмём простую функцию:

def add(a, b):
    return a + b

И посмотрим, во что её превратил компилятор:

$ python3 -m dis helper.py # на Windows py -m dis helper.py
...
Disassembly of <code object add at 0x7f6b650f8570, file "helper.py", line 1>:
  1           RESUME                   0

  2           LOAD_FAST_LOAD_FAST      1 (a, b)
              BINARY_OP                0 (+)
              RETURN_VALUE

Читается это просто:

  • LOAD_FAST_LOAD_FAST — загрузить в стек значения a и b;
  • BINARY_OP (+) — сложить два значения;
  • RETURN_VALUE — вернуть результат.

Набор инструкций байт-кода меняется от версии к версии Python. Поэтому на вашей версии вывод может немного отличаться (я запускал на Python 3.13). Это нормально, учить инструкции наизусть не нужно, главное понять идею.

Каталог pycache

Теперь поговорим о каталоге __pycache__, который наверняка вы видели рядом со своими программами. Давайте разберёмся на примере. Создадим два файла.

Файл helper.py с функцией:

def add(a, b):
    return a + b

И файл program.py, который эту функцию использует:

import helper

print("main", helper.add(2, 3))

Запустим программу и посмотрим, что появилось в каталоге:

$ python3 program.py
main 5

$ ls -R
.:
helper.py  __pycache__  program.py

./__pycache__:
helper.cpython-313.pyc

Обратите внимание на два момента:

  1. Файл program.pyc не появился. Байт-код сохраняется только для импортируемых модулей (у нас это helper.py). Файл, который вы запускаете напрямую, компилируется в памяти при каждом запуске, а на диск не записывается.
  2. В имени helper.cpython-313.pyc указана реализация и версия Python (cpython-313 это CPython 3.13). Байт-код зависит от версии интерпретатора. Если вы обновите Python, то в каталоге появятся новые файлы для новой версии, а старые станут не нужны.

Зачем всё это нужно? Чтобы не компилировать модули каждый раз. Пока исходный файл не менялся, Python берёт готовый байт-код из __pycache__. Если вы изменили helper.py, то Python заметит это (он сверяет время изменения и размер файла, которые записаны внутри .pyc) и скомпилирует модуль заново.

Важно понимать, что это ускоряет только загрузку модулей, то есть запуск программы. Сама программа от этого быстрее работать не станет, ведь байт-код всё равно выполняет виртуальная машина.

Что ещё полезно знать про __pycache__:

  • Каталог можно смело удалять, Python создаст его заново.
  • Если вы используете git, добавьте __pycache__/ в файл .gitignore, коммитить эти файлы не нужно.
  • Запретить создание .pyc можно ключом -B или переменной окружения:
$ python3 -B program.py
$ export PYTHONDONTWRITEBYTECODE=1
  • Переместить все .pyc в отдельный каталог можно переменной PYTHONPYCACHEPREFIX.
  • Скомпилировать все модули заранее (например, при сборке пакета или образа) можно так:
$ python3 -m compileall .

Другие реализации Python

Раз Python это просто язык, а интерпретатор можно написать по-разному, то разные люди сделали свои реализации. Вот самые известные:

  • CPython — эталонная реализация, о которой мы говорили выше. Написана на C.
  • PyPy — реализация со встроенным JIT-компилятором. Он на лету превращает часто выполняемые участки кода в машинный код. Поэтому долгоживущие программы с большим количеством вычислений на PyPy часто работают быстрее, чем на CPython. Но PyPy обычно отстаёт от CPython по поддерживаемой версии языка, и не все библиотеки на нём работают.
  • GraalPy — реализация для платформы GraalVM (Java). Позволяет запускать Python и использовать его из Java-программ. Поддерживает Python 3.
  • IronPython — реализация для платформы .NET. Версия 3.4 за основу берёт синтаксис Python 3.4, хотя некоторые возможности новее, например f-строки.
  • Jython — реализация для JVM, компилирует код в байт-код Java. Но она так и осталась на Python 2.7, а последний релиз вышел в августе 2024 года. Для нового кода на Python 3 она не подходит.
  • MicroPython — урезанная реализация для микроконтроллеров, где нет места для полноценного CPython.

Для большинства задач, в том числе для системного администрирования, достаточно обычного CPython.

Что изменилось в последних версиях

С 2022 года интерпретатор Python сильно изменился, и это стоит знать.

Python 3.11 (2022 год) заметно ускорил CPython. Виртуальная машина научилась подставлять более быстрые варианты инструкций для часто выполняемого кода.

Python 3.13 (2024 год) добавил два экспериментальных режима: JIT-компилятор и сборку без GIL.

Python 3.14 (октябрь 2025 года) сделал сборку без GIL официально поддерживаемой. А в официальных сборках для Windows и macOS появился экспериментальный JIT.

Python 3.15 (октябрь 2026 года) улучшил JIT: на Linux x86-64 он примерно на 7-8% быстрее обычного интерпретатора. Но он по-прежнему экспериментальный и по умолчанию выключен. Также в 3.15 кодировкой по умолчанию стал UTF-8.

Разберёмся, что это за зверьки.

GIL и сборка без GIL

GIL (Global Interpreter Lock, глобальная блокировка интерпретатора) это механизм в CPython, который позволяет выполнять байт-код только одному потоку в один момент времени. Поэтому несколько потоков (threading) не могут одновременно загрузить несколько ядер процессора вычислениями на Python. Для таких задач приходилось использовать несколько процессов (multiprocessing).

Сборка free-threaded («свободные потоки») работает без GIL, и потоки могут выполняться по-настоящему параллельно. В Python 3.14 она стала официально поддерживаемой, но не стала сборкой по умолчанию. Выбрано ли это решение окончательно, пока неизвестно. У такой сборки есть минусы: однопоточный код может работать чуть медленнее, а расширения на C должны быть адаптированы.

Исполняемый файл такой сборки обычно называется с буквой t, например python3.14t. Проверить, включён ли GIL, можно так (метод появился в Python 3.13):

$ python3 -c "import sys; print(sys._is_gil_enabled())"
True

Если вернулось True, то GIL работает. В сборке без GIL вы увидите False.

JIT-компилятор

JIT (Just-In-Time, «точно в срок») компилятор превращает часто выполняемые участки байт-кода в машинный код прямо во время работы программы. Так делает PyPy, а теперь такой механизм появился и в самом CPython. Пока он экспериментальный, по умолчанию выключен и в некоторых программах может даже замедлять выполнение. Поэтому в продакшене его пока лучше не включать.

Забавно, что граница между «компилируемыми» и «интерпретируемыми» языками стирается всё сильнее: исходный код превращается в байт-код, а часть байт-кода потом в машинный код.

Итоги

Подведём итоги:

  • Python принято называть интерпретируемым языком, но внутри интерпретатора есть компилятор и виртуальная машина.
  • Компилятор превращает код в байт-код, а виртуальная машина (PVM) его выполняет.
  • Байт-код импортируемых модулей сохраняется в каталоге __pycache__. Это ускоряет запуск программы, а не её выполнение. Сам запускаемый файл на диск не сохраняется.
  • Основная реализация Python называется CPython, она написана на C. Есть и другие: PyPy, GraalPy, IronPython и так далее.
  • В новых версиях CPython появились экспериментальный JIT и сборка без GIL.

Ссылки


Если понравилась статья, подпишись на мой канал в VK или Telegram .