Python — компилируемый или интерпретируемый язык
Python — интерпретируемый язык, но CPython сначала компилирует код в байт-код. Разбираем .pyc, __pycache__, модуль dis, JIT, PyPy и как отвечать на экзамене.
Содержание · 9 разделов
- Короткий ответ для экзамена и собеседования
- Чем компиляция отличается от интерпретации
- Как CPython выполняет программу
- Синтаксическая ошибка останавливает весь файл
- Байт-код можно увидеть: модуль dis
- Интерпретатор подстраивается под данные
- Компилятор доступен из кода
- Файлы .pyc и папка pycache
- Как скомпилировать .pyc вручную и запустить его
- Как отключить создание .pyc
- Python, C и Java: сравнение
- Реализации Python: от интерпретатора до компилятора
- Экспериментальный JIT в CPython
- Что это меняет на практике
- Частые вопросы
- Можно ли скомпилировать Python-программу в exe?
- Защищает ли распространение .pyc исходный код?
- Почему Python медленнее C, если он тоже компилирует код?
- Какие ещё языки называют интерпретируемыми?
- Python выполняет код построчно?
- Итог
Если отвечать одним словом на вопрос, компилируемый или интерпретируемый язык Python, — интерпретируемый. Программу запускают прямо из исходного файла .py, без отдельного шага сборки в исполняемый файл. Внутри всё устроено на два этапа: стандартный интерпретатор CPython сначала компилирует исходный код в байт-код, а затем выполняет этот байт-код на своей виртуальной машине. Компиляция в Python есть, но не в машинный код процессора, и происходит она незаметно для программиста. Поэтому на собеседовании к ответу «интерпретируемый» ждут уточнения про байт-код.
Короткий ответ для экзамена и собеседования
Тест или экзамен по информатике. На вопрос «Python является интерпретируемым языком или компилируемым языком?» отвечайте: интерпретируемым. Такая классификация принята в учебниках, и её же использует глоссарий официальной документации Python, оговаривая, что граница размыта из-за компилятора байт-кода.
Устный ответ или собеседование. Хватит трёх предложений:
- Строго говоря, интерпретируемой или компилируемой бывает не сам язык, а его реализация.
- Эталонная реализация CPython компилирует исходный код в байт-код (кэширует его в файлах
.pyc) и исполняет байт-код в виртуальной машине, то есть это гибридная схема, как у Java, но без явного шага компиляции. - Есть и другие реализации: PyPy с JIT-компилятором, MicroPython для микроконтроллеров, а Cython и Nuitka переводят Python-код в C.
Если просят одним словом — «интерпретируемый». Если просят объяснить — схема «исходник → байт-код → виртуальная машина».
Чем компиляция отличается от интерпретации
- Компилятор заранее переводит всю программу в другой язык и сохраняет результат. Классический пример — компилятор C: из файла
hello.cполучается исполняемый файл с машинным кодом, который запускается без компилятора. - Интерпретатор выполняет программу сам: читает её и сразу совершает описанные действия. Чтобы запустить программу, интерпретатор нужен каждый раз.
- Байт-код — промежуточное представление программы: набор простых команд для виртуальной машины, не привязанный к конкретному процессору.
- Виртуальная машина — программа, которая исполняет байт-код. У Python её роль выполняет цикл внутри интерпретатора CPython, у Java — JVM.
- JIT-компиляция (just-in-time, «точно в срок») — перевод часто выполняемых участков байт-кода в машинный код прямо во время работы программы.
Большинство современных языков используют смесь этих подходов, поэтому деление «компилируемый / интерпретируемый» описывает скорее привычный способ запуска, чем строгое свойство языка.
Как CPython выполняет программу
CPython — интерпретатор, который скачивают с python.org и который стоит в большинстве систем. Когда вы выполняете python hello.py (в macOS и Linux — python3 hello.py), происходит следующее:
- Исходный текст разбирается на токены и проверяется синтаксис.
- Из текста строится абстрактное синтаксическое дерево (AST) — структура программы.
- Компилятор CPython превращает дерево в байт-код и упаковывает его в объект кода (
code object). - Виртуальная машина выполняет байт-код инструкция за инструкцией.
Первые три шага и есть компиляция. Она работает с целым файлом сразу, поэтому распространённое объяснение «Python выполняет программу строка за строкой» неточно. Проверить это можно за минуту.
Синтаксическая ошибка останавливает весь файл
Сохраните файл errors_syntax.py, где во второй строке не закрыта скобка:
print("Первая строка")
print("Вторая строка"
Если бы Python выполнял код построчно, сначала напечаталась бы первая строка. Но ничего не печатается: файл не компилируется в байт-код, и до выполнения дело не доходит.
File "errors_syntax.py", line 2
print("Вторая строка"
^
SyntaxError: '(' was never closed
Теперь другой файл, errors_runtime.py, синтаксически верный, но с несуществующей переменной:
print("Первая строка")
print(undefined_name)
Компиляция проходит успешно, первая строка выполняется, и только на второй интерпретатор обнаруживает ошибку:
Первая строка
Traceback (most recent call last):
File "errors_runtime.py", line 2, in <module>
print(undefined_name)
^^^^^^^^^^^^^^
NameError: name 'undefined_name' is not defined
Это главное практическое следствие: синтаксис Python проверяет до запуска, а ошибки имён, типов и значений — только когда выполнение дошло до проблемной строки. Как читать такие сообщения, разобрано в статье «Ошибки в Python и traceback». Ловить ошибки типов заранее помогают аннотации типов и проверка mypy.
Байт-код можно увидеть: модуль dis
Модуль dis из стандартной библиотеки показывает байт-код в читаемом виде. Возьмём файл hello.py:
name = "Python"
print("Привет,", name)
Дизассемблируем его из командной строки:
python3 -m dis hello.py # macOS и Linux
py -m dis hello.py # Windows
Вывод в Python 3.14:
0 RESUME 0
1 LOAD_CONST 0 ('Python')
STORE_NAME 0 (name)
2 LOAD_NAME 1 (print)
PUSH_NULL
LOAD_CONST 1 ('Привет,')
LOAD_NAME 0 (name)
CALL 2
POP_TOP
LOAD_CONST 2 (None)
RETURN_VALUE
Слева номер строки исходника, дальше инструкции виртуальной машины. Виртуальная машина CPython стековая: LOAD_CONST кладёт на стек строку 'Python', STORE_NAME снимает её со стека и связывает с именем name. Во второй строке на стек попадают функция print и два аргумента, CALL 2 вызывает функцию с двумя аргументами, а POP_TOP выбрасывает её результат, потому что он не нужен. Последние две инструкции завершают выполнение модуля.
Байт-код отдельной функции показывает dis.dis():
import dis
def square(x):
return x * x
dis.dis(square)
4 RESUME 0
5 LOAD_FAST_BORROW_LOAD_FAST_BORROW 0 (x, x)
BINARY_OP 5 (*)
RETURN_VALUE
В Python 3.12 та же функция даёт две отдельные инструкции LOAD_FAST вместо объединённой LOAD_FAST_BORROW_LOAD_FAST_BORROW. Байт-код — внутренняя деталь CPython: набор инструкций меняется от версии к версии, и опираться на него в программах нельзя.
Интерпретатор подстраивается под данные
С Python 3.11 CPython использует адаптивный (специализирующий) интерпретатор: если функция много раз вызывается с одними и теми же типами, общие инструкции заменяются специализированными. Это видно с параметром adaptive=True:
import dis
def square(x):
return x * x
for i in range(1000):
square(i)
dis.dis(square, adaptive=True)
4 RESUME_CHECK 0
5 LOAD_FAST_BORROW_LOAD_FAST_BORROW 0 (x, x)
BINARY_OP_MULTIPLY_INT 5 (*)
RETURN_VALUE
Универсальное умножение BINARY_OP превратилось в BINARY_OP_MULTIPLY_INT — быструю версию для целых чисел. Если в функцию передать строку или список, интерпретатор вернётся к общей инструкции. Это ещё не JIT-компиляция в машинный код, но уже оптимизация во время выполнения.
Компилятор доступен из кода
Встроенная функция compile() вызывает тот же компилятор, что работает при запуске файла. Она возвращает объект кода, который выполняют exec() или eval():
code = compile("a + b", "<string>", "eval")
print(type(code)) # → <class 'code'>
print(eval(code, {"a": 2, "b": 3})) # → 5
В обычной работе compile() почти не нужна, но наглядно показывает, что «интерпретируемый» Python содержит полноценный компилятор.
Файлы .pyc и папка pycache
Чтобы не компилировать модули при каждом запуске, CPython сохраняет байт-код на диск. Создадим два файла: модуль square.py и программу main.py, которая его импортирует.
# square.py
def square(x):
return x * x
# main.py
import square
print(square.square(7)) # → 49
После запуска python main.py рядом появится папка __pycache__:
main.py
square.py
__pycache__/
square.cpython-314.pyc
Что важно знать про эти файлы:
- Кэшируются только импортируемые модули. Байт-код главного скрипта, который передали интерпретатору, на диск не пишется и компилируется заново при каждом запуске. Поэтому файла
main.cpython-314.pycнет. - Имя файла содержит реализацию и версию.
cpython-314означает CPython 3.14. Разные версии Python хранят кэш рядом, не мешая друг другу. При запуске с флагом-Oпоявляется отдельный файлsquare.cpython-314.opt-1.pyc. - Кэш обновляется сам. В
.pycзаписаны время изменения и размер исходного файла. Еслиsquare.pyизменился, Python перекомпилирует модуль и перезапишет кэш. С Python 3.7 поддерживаются и.pycс хешем содержимого исходника вместо времени изменения. - Папку можно удалить. Python создаст её снова при следующем импорте. Если после переименования файла программа ведёт себя странно, удаление
__pycache__— безопасный первый шаг. - В git её не добавляют. Строки
__pycache__/и*.pycесть в стандартных шаблонах.gitignoreдля Python.
Как скомпилировать .pyc вручную и запустить его
Модуль py_compile компилирует один файл, compileall — все файлы в папке и подпапках:
python3 -m py_compile hello.py # macOS и Linux
python3 -m compileall .
py -m py_compile hello.py # Windows
py -m compileall .
Файл .pyc можно запустить даже без исходника:
python3 __pycache__/hello.cpython-314.pyc
Привет, Python
Но только той же версией Python. Если запустить этот файл интерпретатором 3.12, он откажется:
RuntimeError: Bad magic number in .pyc file
«Магическое число» в начале .pyc обозначает версию формата байт-кода. Поэтому .pyc нельзя считать переносимой программой: в отличие от .class в Java, байт-код Python меняется с каждой минорной версией.
Как отключить создание .pyc
- Флаг
-B:python -B main.py. - Переменная окружения
PYTHONDONTWRITEBYTECODE=1— так часто делают в Docker-образах, где кэш на диске не нужен. - Переменная
PYTHONPYCACHEPREFIX(с Python 3.8) переносит весь кэш в отдельный каталог вместо папок__pycache__рядом с кодом.
Python, C и Java: сравнение
| Критерий | C | Java | Python (CPython) |
|---|---|---|---|
| Во что переводится код | В машинный код процессора | В байт-код JVM (файлы .class) | В байт-код CPython (кэш в .pyc) |
| Когда переводится | Заранее, отдельной командой gcc | Заранее, отдельной командой javac | Автоматически при запуске и импорте |
| Что исполняет программу | Процессор напрямую | Виртуальная машина JVM с JIT-компилятором | Виртуальная машина CPython; JIT пока экспериментальный |
| Что нужно для запуска | Только исполняемый файл | Установленная JVM | Установленный интерпретатор нужной версии |
| Когда находятся ошибки типов | При компиляции | При компиляции | Во время выполнения |
| Переносимость результата | Нужна сборка под каждую ОС и процессор | Один .class работает на любой JVM | Исходник .py работает везде, .pyc — только в той же версии |
| Как обычно называют язык | Компилируемый | Компилируемый (в байт-код) | Интерпретируемый |
По устройству Java и Python похожи: оба переводят исходник в байт-код и выполняют его виртуальной машиной. Java называют компилируемой, потому что шаг компиляции явный и отдельный, javac проверяет типы ещё до запуска, а JVM по умолчанию компилирует горячий код в машинный. Python же компилирует незаметно при каждом запуске, проверяет типы во время выполнения, а основная работа в CPython идёт в интерпретаторе байт-кода.
Отсюда и разница в скорости: цикл на чистом Python заметно медленнее такого же цикла на C. Подробное сравнение по скорости, памяти и задачам — в статье «Python или C/C++: что выбрать».
Реализации Python: от интерпретатора до компилятора
Python — это спецификация языка, и у неё несколько реализаций с разными способами выполнения кода.
| Реализация | Как выполняет код | Где пригодится |
|---|---|---|
| CPython | Байт-код и интерпретатор, адаптивная специализация, экспериментальный JIT | Эталонная реализация, используется по умолчанию |
| PyPy | Байт-код и трассирующий JIT-компилятор в машинный код | Долго работающий код на чистом Python |
| MicroPython | Собственный компактный байт-код; файлы .mpy готовят заранее утилитой mpy-cross | Микроконтроллеры: ESP32, Raspberry Pi Pico |
| Cython | Переводит Python-код (при желании с объявлениями типов C) в код на C, затем компилятор C собирает модуль расширения | Ускорение отдельных функций и связка с библиотеками на C |
| Nuitka | Переводит Python-программу в C и компилирует её компилятором C в исполняемый файл или модуль | Сборка программы в исполняемый файл |
Несколько уточнений:
- PyPy поддерживает Python 3.12, 3.11 и 2.7. Разработчики PyPy оценивают его как в среднем примерно в 4 раза более быстрый, чем CPython 3.11. Выигрыш заметен на долгих вычислениях на чистом Python; короткие скрипты и код, который почти всё время проводит в библиотеках на C, ускоряются мало.
- Cython и Nuitka требуют установленного компилятора C (в Windows — Microsoft C++ Build Tools, в macOS — Xcode Command Line Tools, в Linux — gcc или clang). Скомпилированный код по-прежнему опирается на среду выполнения CPython, поэтому семантика языка не меняется.
- MicroPython реализует подмножество языка и стандартной библиотеки, сокращённое ради работы на устройствах с очень небольшим объёмом памяти.
Экспериментальный JIT в CPython
JIT-компилятор появляется и в самом CPython, но пока не включён по умолчанию:
- Python 3.13 — экспериментальный JIT, доступный только при сборке интерпретатора из исходников с опцией
--enable-experimental-jit. - Python 3.14 — JIT входит в официальные сборки для Windows и macOS, но выключен; включается переменной окружения
PYTHON_JIT=1. Разработчики не рекомендуют его для продакшена и оценивают эффект в диапазоне от замедления на 10% до ускорения на 20% в зависимости от нагрузки. - Python 3.15 (вышел 9 октября 2026 года) — JIT существенно доработан, но по-прежнему экспериментальный. По данным релиза, на наборе тестов pyperformance он в среднем быстрее обычного интерпретатора на 7–8% на x86-64 Linux и на 11–12% на macOS с процессорами ARM.
Проверить, есть ли JIT в вашей сборке и включён ли он, можно через служебное пространство имён sys._jit (Python 3.14+):
python3 -c "import sys; print(sys._jit.is_available(), sys._jit.is_enabled())"
py -c "import sys; print(sys._jit.is_available(), sys._jit.is_enabled())"
Первое значение показывает, поддерживает ли сборка JIT, второе — включён ли он в текущем процессе. В сборках без JIT (такими бывают сторонние дистрибутивы Python) оба значения будут False. Даже с включённым JIT CPython сначала компилирует код в байт-код: JIT переводит в машинный код только часто выполняемые участки этого байт-кода.
Что это меняет на практике
- Запуск без сборки. Изменили файл — сразу запускаете. Интерактивный режим (REPL) компилирует и выполняет каждую введённую команду, поэтому Python удобен для экспериментов. Способы запуска собраны в статье «Как запустить Python-скрипт».
- Ошибки во время выполнения. Опечатка в имени переменной в редко выполняемой ветке
ifпроявится только тогда, когда программа туда попадёт. Тесты, линтеры и проверка типов компенсируют это. - Скорость. Интерпретация байт-кода медленнее машинного кода. Тяжёлые вычисления в Python обычно выполняют библиотеки на C (NumPy, pandas) или ускоряют через PyPy, Cython и Numba.
- Распространение программы. Пользователю нужен интерпретатор нужной версии. Чтобы отдать программу одним файлом, её упаковывают вместе с интерпретатором — подробнее в статье «Как сделать exe из Python-скрипта».
Частые вопросы
Можно ли скомпилировать Python-программу в exe?
Да, но чаще всего программу упаковывают, а машинного кода из неё не получается. PyInstaller складывает в один файл интерпретатор, байт-код и библиотеки: программа по-прежнему выполняется интерпретатором. Компиляцией в машинный код через C ближе всего к этому Nuitka, но и ей нужна среда выполнения CPython внутри сборки.
Защищает ли распространение .pyc исходный код?
Почти нет. Байт-код легко посмотреть через dis, а декомпиляторы восстанавливают из него код, близкий к исходному (для свежих версий Python они появляются с задержкой). Кроме того, .pyc работает только в той версии Python, которой он создан. Если код действительно нужно скрыть, логику выносят на сервер.
Почему Python медленнее C, если он тоже компилирует код?
CPython компилирует код только в байт-код. Каждую инструкцию байт-кода интерпретатор выполняет через свою программу: проверяет типы объектов, создаёт новые объекты для результатов, обновляет счётчики ссылок. Компилятор C знает типы заранее и выдаёт готовые машинные инструкции, поэтому этой работы во время выполнения нет.
Какие ещё языки называют интерпретируемыми?
Обычно JavaScript, PHP, Ruby и Perl. У всех современных реализаций внутри тоже есть компиляция: PHP и Ruby компилируют код в байт-код, движок V8 для JavaScript компилирует горячий код JIT-компилятором. В учебных классификациях к компилируемым относят C, C++, Go, Rust, а Java и C# — к компилируемым в байт-код.
Python выполняет код построчно?
Нет: CPython сначала компилирует в байт-код целый файл, поэтому синтаксическая ошибка в последней строке не даст выполниться и первой. Построчно выглядит только интерактивный режим, где каждая введённая команда компилируется и выполняется отдельно. Импортируемые модули компилируются в момент импорта, поэтому синтаксическая ошибка в модуле проявится, когда программа до него дойдёт.
Итог
- Короткий ответ: Python — интерпретируемый язык; так отвечают на тестах и экзаменах.
- Точный ответ: CPython компилирует исходный код в байт-код, кэширует его в
.pycв папке__pycache__и выполняет на своей виртуальной машине. Байт-код можно посмотреть модулемdis. - Интерпретируемость — свойство реализации: PyPy использует JIT-компилятор, в CPython 3.14 и 3.15 JIT экспериментальный, а Cython и Nuitka переводят Python-код в C.
- Что такое Python, где он применяется и чем хорош, рассказано в статье «Python: что это за язык и для чего он нужен», а с чего начать обучение — в учебном маршруте «Python с нуля».
- #Python
- #CPython
- #Байт-код
- #Интерпретатор
- #Для начинающих