Как скомпилировать Python
Я хочу рассказать об удивительном событии, о котором я узнал пару месяцев назад. Оказывается, одна популярная python-утилита уже более года распространяется в виде бинарных файлов, которые компилируются прямо из python. И речь не про банальную упаковку каким-нибудь PyInstaller-ом, а про честную Ahead-of-time компиляцию целого python-пакета. Если вы удивлены так же как и я, добро пожаловать под кат.
Объясню, почему я считаю это событие по-настоящему удивительным. Существует два вида компиляции: Ahead-of-time (AOT), когда весь код компилируется до запуска программы и Just in time compiler (JIT), когда непосредственно компиляция программы под требуемую архитектуру процессора осуществляется во время ее выполнения. Во втором случае первоначальный запуск программы осуществляется виртуальной машиной или интерпретатором.
Если сгруппировать популярные языки программирования по типу компиляции, то получим следующий список:
- Ahead-of-time compiler: C, C++, Rust, Kotlin, Nim, D, Go, Dart;
- Just in time compiler: Lua, С#, Groovy, Dart.
В python из коробки нет JIT компилятора, но отдельные библиотеки, предоставляющие такую возможность, существуют давно
Смотря на эту таблицу, можно заметить определенную закономерность: статически типизированные языки находятся в обеих строках. Некоторые даже могут распространяться с двумя версиями компиляторов: Kotlin может исполняться как с JIT JavaVM, так и с AOT Kotlin/Native. То же самое можно сказать про Dart (версии 2). A вот динамически типизированные языки компилируются только JIT-ом, что впрочем вполне логично.
При запуске виртуальная машина сначала накапливает информацию о типах переменных, затем после накопления статистики, запускается компиляция наиболее нагруженных частей программы. Виртуальная машина отслеживает типы аргументов и переключает выполнение программы между уже скомпилированными и не скомпилированными участками кода в зависимости от текущих значений переменных.
При использовании JIT компиляции типы не очень то и нужны, ведь информация о типах собирается во время работы программы. Поэтому все популярные динамически типизированные языки программирования распространяются именно с JIT компилятором. Но как быть с AOT компиляцией кода, в котором нет типов? Меня очень заинтересовал этот вопрос, и я полез разбираться.
Итак, вернемся к утилите, о которой говорилось в начале статьи. Речь про mypy — наиболее популярный синтаксический анализатор python-кода.
С апреля 2019 года эта утилита распространяется в скомпилированном виде, о чем рассказывается в блоге проекта. А для компиляции используется еще одна утилита от тех же авторов — mypyc. Погуглив немного, я нашел достаточно большую статью “Путь к проверке типов 4 миллионов строк Python-кода” про становление и развитие mypy (на Хабре доступен перевод: часть 1, часть 2, часть 3). Там немного рассказывается о целях создания mypyc: столкнувшись с недостаточной производительностью mypy при разборе крупных python-проектов в Dropbox, разработчики добавили кеширование результатов проверки кода, а затем возможность запуска утилиты как сервиса. Но исчерпав очевидные возможности оптимизации, столкнулись с выбором: переписать все на go или на cython. В результате проект пошел по третьему пути — написание своего AOT python-компилятора.
Дело в том, что для правильной работы mypy и так необходимо построить то же синтаксическое дерево, что и интерпретатору во время исполнения кода. То есть mypy уже “понимает” python, но использует эту информацию только для статистического анализа, а вот mypyc может преобразовывать эту информацию в полноценный бинарный код.
Думаю тут многие решили, что разобрались в вопросе того, как скомпилировать динамически типизированный python-код. Python c версии 3.4 поддерживает аннотацию типов, а mypy как раз и используется для проверки корректности аннотаций. Получается, python как бы уже и не динамически типизированный язык, что позволяет применить AOT компиляцию. Но загвоздка в том, что mypyc может компилировать и неаннотированный код!
Функция bubble_sort
Для примера рассмотрим функцию сортировки “пузырьком”. Файл lib.py:
def bubble_sort(data): n = len(data) for i in range(n - 1): for j in range(n - i - 1): if data[j] > data[j + 1]: buff = data[j] data[j] = data[j + 1] data[j + 1] = buff return data
У типов нет аннотаций, но это не мешает mypyc ее скомпилировать. Чтобы запустить компиляцию, нужно установить mypyc. Он не распространяется отдельным пакетом, но если у вас установлен mypy, то и mypyc уже присутствует в системе! Запускаем mypyc, следующей командой:
> mypyc lib.py
После запуска в проекте будут созданы следующие директории:
- .mypy_cache — mypy кэш, mypyc неявно запускает mypy для разбора программы и получения AST;
- build — артефакты сборки;
- lib.cpython-38-x86_64-linux-gnu.so — собственно сборка под целевую платформу. Данный файл представляет из себя готовый CPython Extension.
CPython Extension — встроенный в CPython механизм взаимодействия с кодом, написанным на С/C++. По сути это динамическая библиотека, которую CPython умеет загружать при импорте нашего модуля lib. Через данный механизм осуществляется взаимодействие с модулями, написанными на python.
Компиляция состоит из двух фаз:
- Компиляция python кода в код С;
- Компиляция С в бинарный .so файл, для этого mypyc сам запускает gcc (gcc и python-dev также должен быть установлены).
Файл lib.cpython-38-x86_64-linux-gnu.so имеет преимущество перед lib.py при импорте на соответствующей платформе, и исполняться теперь будет именно он.
Ну и давайте сравним производительность модуля до и после компиляции. Для этого создадим файл main.py с кодом запуска сортировки:
import lib data = lib.bubble_sort(list(range(5000, 0, -1))) assert data == list(range(1, 5001))
Получим примерно следующие результаты:
Компиляция Python

Предположим, вы разработали приложение или библиотеку на Python и уже готовитесь передать его / её заказчику. И в этот момент возникают вопросы, о которых многие даже не задумываются.
Во-первых, так может оказаться, что вы разработали супер крутой алгоритм, которого ни у кого нет, и показывать его хочется только избранным.
Во-вторых, возникает вопрос окружения — хочется быть уверенным, что заказчик справится с установкой правильной версии Python и всех вспомогательных библиотек, но это не всегда простая задача. Было бы удобно упаковать приложение в автономный исполняемый файл.
И, наконец, хочется, чтобы конечное приложение работало быстрее, чем в среде разработки.
И вот тут настало время скомпилировать Python-код. Меня зовут Руслан, я старший разработчик компании «Цифровое проектирование». Сегодня я расскажу, как выбрать тот самый компилятор из множества доступных.
AOT/JIT
Компиляция – это сборка программы, включающая: трансляцию всех модулей программы, написанных на языке программирования высокого уровня, в эквивалентные программные модули на низкоуровневом языке, близком к машинному коду, или на машинном языке и сборку исполняемой программы. Существует два вида компиляции:
- AOT-компиляция (ahead-of-time) – компиляция перед исполнением программы. Т.е. программа компилируется один раз, в результате компиляции получается исполняемый файл.
- JIT-компиляция (just-in-time) – компиляция во время исполнения программы. Т.е. программа (а точнее, блоки программы) компилируется много раз — при каждом запуске.
Бенчмарк
Так как одной из целей является ускорение, необходимо оценить, насколько быстро работает скомпилированный код. В качестве бенчмарка будем использовать pyperfomance. К сожалению, pyperfomance не подошел для Cython и Pythran, потому что не позволяет визуализировать все возможности языка. Ускорения для Cython без модификации кода получить не удалось, а Pythran не умеет в пользовательские классы. Для них воспользуемся вычислением числа пи:
def approximate_pi(n): step = 1.0 / n result = 0 for i in range(n): x = (i + 0.5) * step result += 4.0 / (1.0 + x * x) return step * result
Эксперименты будем проводить на процессоре Intel Core i7 10510U. На CPython 3.9.7 время вычисления числа пи до 100.000.000 знака заняло 5.82 секунды.
AOT-компиляция Python
PyInstaller
PyInstaller упаковывает приложения Python в автономные исполняемые файлы в Windows, GNU / Linux, Mac OS X, FreeBSD, Solaris и AIX.
Устанавливается через pip:
pip install pyinstaller
После установки для создания исполняемого файла достаточно выполнить команду:
В результате будет создано:
- *.spec – файл спецификации (используется для ускорения будущих сборок приложения, связи файлов данных с приложением, для включения .dll и .so файлов, добавление в исполняемый файл параметров runtime-а Python);
- build/ – директория с метаданными для сборки исполняемого файла;
- dist/ – директория, содержащая все зависимости и исполняемый файл.
Сборку приложения можно настроить с помощью параметров командной строки:
- —name – изменение имени исполняемого файла (по умолчанию, такое же, как у сценария);
- —onefile – создание только исполняемого файла (по умолчанию, папка с зависимостями и исполняемый файл);
- —hidden-import – перечисление импортов, которые PyInstaller не смог обнаружить автоматически;
- —add-data – добавление в сборку файлов данных;
- —add-binary – добавление в сборку бинарных файлов;
- —exclude-module – исключение модулей из исполняемого файла;
- —key – ключ шифрования AES256. Да, приложение не будет содержать исходного кода, но его можно декомпилировать, например, так: Pyinstaller Extractor (.exe → .pyc) и uncompile6 (.pyc → .py). Для скрытия исходного кода можно обфусцировать байт-код Python с помощью шифрования.
У PyInstaller есть ограничения. Он работает с Python 3.5–3.9. Поддерживает создание исполняемых файлов для разных операционных систем, но не умеет выполнять кросскомпиляцию, т. е. необходимо генерировать исполняемый файл для каждой ОС отдельно. Более того, исполняемый файл зависит от пользовательского glibc, т. е. необходимо генерировать исполняемый файл для самой старой версии каждой ОС.
PyInstaller знает о многих Python-пакетах и умеет их учитывать при сборке исполняемого файла. Но не о всех. Например, фреймворк uvicorn практически весь нужно явно импортировать в файл, к которому будет применена команда pyinstaller. Полный список поддерживаемых из коробки пакетов можно посмотреть здесь.
Cython
Cython — это оптимизирующий статический компилятор как для языка программирования Python, так и для расширенного языка программирования Cython. С его помощью можно код на Python транслировать в С и затем скомпилировать в бинарник, совместимый с CPython. Компиляцию придется делать под все операционные системы и архитектуры процессора.
Ставится Cython через pip:
pip install Cython
Рассмотрим его работу на примере с вычислением числа пи:
def approximate_pi(int n): cdef float step cdef float result cdef float x step = 1.0 / n result = 0.0 for i in range(n): x = (i + 0.5) * step result += 4.0 / (1.0 + x * x) return step * result
А что делать, если в нашем проекте несколько файлов, которые нужно скомпилировать? Тогда нужно использовать так называемый сценарий сборки. С его помощью можно модернизировать сборку в зависимости от операционной системы, указывать несколько файлов, которые необходимо скомпилировать, и многое другое.
Создадим файл build.py:
from distutils.core import setup from Cython.Build import cythonize setup( ext_modules=cythonize("bench_cython.pyx"), )
Запустим: python build.py build_ext –-inplace
В результате будет сгенерирован .so/.dll файл.
Nuitka
Nuitka способна упаковывать приложения Python в автономные исполняемые файлы, а также транслировать Python-код в С для его последующей компиляции. Работает с разными версиями Python (2.6, 2.7, 3.3 — 3.9).
Ставится через pip:
pip install nuitka
Для генерации исполняемого файла достаточно выполнить команду:
python -m nuitka —follow-import some_program.py
Для компиляции модуля:
python -m nuitka —module some_module.py
Для компиляции пакета:
python -m nuitka —module some_package —include-package = some_package
Pythran
Pythran – статический компилятор Python, позиционирующий себя как ориентированный на научные вычисления и использующий преимущества многоядерных процессоров и блоков инструкций SIMD. Он транслирует Python-код, аннотированный описаниями интерфейса, в C++. До версии 0.9.5 (включительно) Pythran поддерживал Python 3 и Python 2.7. Последние версии поддерживают только Python 3.
pip install pythran
Генерируем бинарный файл .so:
Pythran по умолчанию не умеет в пользовательские классы, поэтому при попытке их компиляции будет выброшена ошибка:
Top level statements can only be assignments, strings,functions, comments, or imports
Добавим комментарий аннотации функции:
#pythran export approximate_pi(int) def approximate_pi(n): step = 1.0 / n result = 0 for i in range(n): x = (i + 0.5) * step result += 4.0 / (1.0 + x * x) return step * result
Скомпилируем и бенчмарк выдает 0,00007 секунды.
cx-Freeze
cx-Freeze – это набор скриптов и модулей преобразования скриптов Python в исполняемые файлы. cx_Freeze — кроссплатформенный. Он поддерживает Python 3.5.2 и выше.
Ставится с помощью pip:
pip install cx_Freeze
Для генерации исполняемого файла достаточно выполнить команду:
Сборку можно настроить с помощью параметров командной строки:
- -h, —help — справка;
- -O — оптимизировать сгенерированный байт-код согласно PYTHONOPTIMIZE;
- -c, —compress — сжать байт-код в zip-файлах;
- -s, —silent — выводить только предупреждения и ошибки;
- —target-dir=DIR, —install-dir=DIR — каталог, в который следует поместить целевой файл и все зависимые файлы.
Также возможно использование сценария сборки, например, так:
import sys from cx_Freeze import setup, Executable # Dependencies are automatically detected, but it might need fine tuning. build_exe_options = # GUI applications require a different base on Windows (the default is for a # console application). base = None if sys.platform == "win32": base = "Win32GUI" setup( name = "guifoo", version = "0.1", description = "My GUI application!", options = , executables = [Executable("guifoo.py", base=base)])
Сборка исполняемого файла:
python setup.py build
JIT-компиляция Python
JIT-компиляция не позволяет скрывать исходники или создавать автономный исполняемый файл, но дает возможность значительно ускорить выполнение программы.
PyPy
PyPy — интерпретатор языка программирования Python 2.7 и Python 3.7. Он написан на RPython и содержит:
- компилятор байт-кода, отвечающий за создание объектов кода Python из исходного кода пользовательского приложения;
- оценщик байт-кода, ответственный за интерпретацию объектов кода Python;
- стандартное объектное пространство, отвечающее за создание и управление объектами Python, видимыми приложением.
PyPy поддерживает сотни библиотек Python, включая NumPy.
Основные особенности (сравнение с CPython):
- Скорость. При выполнении длительно выполняющихся программ, когда значительная часть времени тратится на выполнение кода Python, PyPy может значительно ускорить ваш код.
- Использование памяти. Программы Python, требующие много памяти (несколько сотен Мб или более), могут занимать меньше места, чем в CPython. Однако это не всегда так, поскольку зависит от множества деталей. Также базовый уровень потребления оперативной памяти выше, чем у CPython.
Скачать PyPy можно с здесь. После скачивания PyPy готов к запуску после распаковки архива. Если необходимо сделать PyPy доступным для всей системы, достаточно поместить символическую ссылку на исполняемый файл pypy в /usr/local/bin. Также можно поставить с помощью pyenv.
PyPy работает на Mac, Linux (не все дистрибутивы) или Windows.
Для запуска кода с помощью PyPy вместо команды python3 (как c помощью CPython) достаточно воспользоваться командой pypy3:
Pyston
Pyston — это форк CPython 3.8.8 с дополнительной оптимизацией производительности. В настоящее время он поддерживает установку только из исходников. Или с помощью pyenv.
В Pyston поддерживаются все возможности CPython, в том числе C API для разработки расширений на языке Си. Среди основных отличий Pyston от CPython помимо общих оптимизаций выделяется использование DynASM JIT и inline-кэширования.
Заключение
Итак, мы рассмотрели 5 фреймворков AOT-компиляции Python. Для любителей аналитики, ниже приведена таблица со сравнительным анализом.
PyInstaller
Cython
Nuitka
Pythran
cx-Freeze
Генерация автономных исполняемых файлов
Компиляция python-модуля в исполняемый файл, совместимый с CPython
Можно ли скомпилировать py файл в exe, но через код
Моя задача сделать скомпилированный Python скрипт, который будет по шаблону создавать код и его компилировать (тоже в exe). Обычно пользуюсь PyInstaller, но не представляю как запустить компиляцию PyInstaller на ПК, на котором нет Python и PIP
Отслеживать
задан 9 фев 2022 в 15:25
1 2 2 бронзовых знака
Сперва установить Python и PIP, а затем запустить PyInstaller ¯\_(ツ)_/¯
9 фев 2022 в 15:42
имелось ввиду данное: допустим, у нас есть клиент (который не имеет Python и PIP), которому я скину программу, а программа должна уметь самостоятельно компилировать .py файлы.Короче внутри программы должен быть компилятор Python.
9 фев 2022 в 16:03
@LEANX т.е. у него не развернут питон и пип, но у него откуда не пойми есть исходники ?
9 фев 2022 в 16:17
@LEANX И эти манипуляции проще, чем установить питон? Есть кстати такой виндовый портативный дистрибьютив питона winpython.github.io/#portable
Самый простой способ скомпилировать python файл в exe

Я перепробовал кучу разных утилит для компиляции python скрипта в .exe и везде были проблемы или не собирался файл, лог ошибками сыпал, файл собирался но не запускался и т.д.
И помог мне самый простой и легкий способ, расскажу о нем в трех шагах:
1. Устанавливаем пакет для сборки скрипта в exe
pip install pyinstaller
2. Переходим в папку со скриптом
cd c:\Свой_путь_до_скрипта
3. Запускаем компиляцию
pyinstaller --onefile main.py
Вместо main.py подставляем свое имя скрипта если это отличается.
После сборки в папке проекта рядом с вашим скриптом появится папка dist в которой и будет находится exe файл.
Этот способ работает с установленными сторонними библиотеками python.
15.04.2019 15:02 15.04.2019 15:02:03
Помогла статья?
Поддержи автора, любой суммой.
21.10.2019 22:06:03
в конце выдаёт ошибку
return code_func(co.co_argcount, co.co_kwonlyargcount, co.co_nlocals, co.co_stacksize,
TypeError: an integer is required (got type bytes)
11.11.2019 14:33:23
10.12.2019 18:19:05
Включи голову, тебе все одно и тоже пишут, ты когда пишешь «pip install pyinstaller» он тебе и так последнюю ставит.
Инструкция не рабочая.

10.12.2019 23:17:03
pip install pyinstaller может не устанавливать последнюю версию пакета если сам pip старой версии
Правильнее будет обновить pip командой:
python -m pip install --upgrade pip
а затем устанавливать pyinstaller
14.12.2019 02:52:39
Решил проблему? Если да, то скажи как?
drtgfgffdf
24.10.2019 18:50:21
У меня тоже самое
29.10.2019 23:18:45
У меня работает, большое спасибо автору за статью.
Garry Galler
13.01.2020 20:37:12
Вот дебилы. Python не компилируется в exe.
07.07.2020 04:07:29
Ты хоть сам понял, что тут написал??
А во что он компилируется?
Я вот лично написал простой скрипт, и сделал .exe-шник из него, а потом запустил его на другом ПК, на котором отродясь не было никакого интерпретатора Python. Всё в итоге отлично запустилось и работает как положено.
Garry Galler
02.08.2021 05:54:11
Я то понял. А вот ты абсолютно не понимаешь в чем разница между скомпилированной программой и упакованной программой. pyinstaller создает архив. Попробуй запомнить это. И архив этот он распаковывает в директорию TEMP и только потом запускает твой код. Не ожидал такого подвоха?! Потому что нужно было матчасть изучать и и читать документацию pyinstaller’а вместо подобных бесполезных статеек.
Timon White
29.01.2021 11:00:43
В итоге дебилом оказался ты сам. прежде чем что-то подобное заявлять, не плохо бы погуглить вопрос.
Garry Galler
02.08.2021 05:47:16
Прошел год. Надеюсь ты понял, что дебилом оказался все-таки ты?
pyinstaller не компилятор. Поэтому Python не компилируется в exe, а тупо упаковывается в SFX архив вместе с готовым оболочкой для запуска кода.
И этот процесс никакого отношения к настоящей компиляции не имеет.
P.S. А теперь иди гуглить, школьник. Запрос такой: что такое компилятор.
12.10.2022 20:19:10
Тебе сколько лет-то быдло, чтобы дебилом человека с порога называть?
13.02.2020 17:29:03
Ребята, инструкция отличная, НО как селениум упаковать?
06.05.2020 12:41:40
«pyinstaller» не является внутренней или внешней
командой, исполняемой программой или пакетным файлом.

06.05.2020 12:44:12
а вы его установили командой pip install pyinstaller ?
23.02.2023 19:50:15
PermissionError: [Errno 13] Permission denied: ‘C:\\Users\\СУ\\Desktop\\dist\\hello.exe.notanexecutable’\

24.02.2023 10:03:50
Доступа к этой папке нет, попробуйте скопировать проект в другую директорю и запустить.
29.12.2020 09:40:59
03.04.2021 22:01:53
пишет
«pyinstaller» не является внутренней или внешней
командой, исполняемой программой или пакетным файлом.

04.04.2021 00:20:35
попробуйте перед вызовом pyinstaller прописать python, вот так:
python pyinstaller --onefile main.py
и убедитесь что у вас pyinstaller действительно установился
08.04.2021 21:25:58
Помогите пожалуйста всё делал по инструкции, но сразу после ввода команды появляется ошибка:
SyntaxError: Non-UTF-8 code starting with ‘\xe0’ in file C:\Users\Администратор\AppData\Local\Programs\Python\Python39\Scripts\pyinstaller-script.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

09.04.2021 10:04:31
У вас скорее всего файл в кодировке windows-1251, сконвертируйте в UTF-8.
Также вначале скрипта можно прописать:
# -*- coding: utf-8 -*-
09.04.2021 18:38:23
Я писал программу в IntelliJ IDEA там по умолчанию стояла кодировка UTF-8, вашу строчку в начале добавил, но не помогло. Также пробовал пересохранить файл через Блокнот в нужной кодировке тоже не сработало. Ещё у меня PyInstaller сразу не установился сначала пыталась поставиться версия 4.2, но что-то пошло не так поставилась только 3.3.1. Может с этим как-то связано? Также у меня программе используется tkinter и в принципе мне не нужна консоль.
