py2exe и cx_Freeze: альтернативы сборки exe
Сборка exe без PyInstaller: py2exe — старейший упаковщик Windows, cx_Freeze — кросс-платформенный. Общий принцип, recipe на setup.py и правила выбора инструмента.
Редакция Питоники
PyInstaller — самый популярный упаковщик, но не единственный. Два ветерана жанра: py2exe — старейший сборщик для Windows, и cx_Freeze — кросс-платформенный инструмент с тем же типом рецепта. Урок показывает, что у всех упаковщиков внутри общее, чем различаются рецепты и как выбирать инструмент осознанно, а не по привычке.
Общий принцип у всех упаковщиков
Несмотря на разные команды и файлы настройки, результат у всех один: интерпретатор рядом с вашим кодом и зависимостями. Убедимся, собрав «бандлы» от имени трёх упаковщиков и заглянув внутрь каждого:
import os, zipfile
def build(packager):
bundle = packager + '.exe'
with zipfile.ZipFile(bundle, 'w') as z:
z.writestr('runtime.bin', 'interpreter')
z.writestr('__main__.py', "ARG = '" + packager + "'\nprint('Приложение собрано:', ARG)\n")
return bundle
for packager in ('pyinstaller', 'py2exe', 'cx_freeze'):
bundle = build(packager)
with zipfile.ZipFile(bundle) as z:
names = ', '.join(sorted(z.namelist()))
entry = z.read('__main__.py').decode('utf-8')
print(packager + ':', names)
exec(compile(entry, '__main__.py', 'exec'), {'__name__': '__main__'})
os.remove(bundle)
pyinstaller: __main__.py, runtime.bin Приложение собрано: pyinstaller py2exe: __main__.py, runtime.bin Приложение собрано: py2exe cx_freeze: __main__.py, runtime.bin Приложение собрано: cx_freeze
Разница упаковщиков не в содержимом бандла — внутри у всех интерпретатор, байткод и данные, — а в рецепте сборки и удобстве сопровождения. Настоящие бандлы сложнее: бутстрап, платформенные библиотеки, архивы байткода, — но принцип общий.
py2exe: старейший упаковщик Windows
py2exe появился в начале 2000-х и живёт до сих пор. Его ниша — Windows-приложения: консольные утилиты, GUI-программы и, что уникально среди упаковщиков, Windows-службы. Рецепт сборки — файл setup.py, как у библиотек:
# setup.py для py2exe
from distutils.core import setup
import py2exe
setup(console=['main.py'])
python setup.py py2exe
Границы py2exe честные: Windows-only — на других системах его нет вовсе; сообщество скромнее, чем у PyInstaller, поэтому готовых хуков под экзотические библиотеки меньше, и несовместимости разбирать придётся руками.
cx_Freeze: кросс-платформенный рецепт
cx_Freeze собирает под Windows и Linux из одного рецепта, на macOS — с оговорками. Стиль тот же: setup.py с описанием приложения:
# setup.py для cx_Freeze
from cx_Freeze import setup, Executable
setup(
name='mytool',
version='1.0',
executables=[Executable('main.py')],
)
pip install cx_Freeze
python setup.py build
Настройка: excludes, includes и данные
В подробных рецептах опции сборки передают словарём options. Ключевые: excludes убирает ненужные модули, includes добавляет невидимые анализатору, include_files копирует данные рядом с exe:
build_exe_options = {
'packages': ['os'],
'excludes': ['tkinter'],
'include_files': ['config.json'],
}
setup(
name='mytool',
options={'build_exe': build_exe_options},
executables=[Executable('main.py', base='Win32GUI')],
)
Логика excludes и includes легко проверяется на чистом Python: убираем исключённое из списка, добавляем пропущенное в конец:
def resolve_bundle(modules, options):
excluded = set(options.get('excludes', []))
included = sorted(options.get('includes', []))
result = []
for name in modules:
if name in excluded:
continue
result.append(name)
for name in included:
if name not in result:
result.append(name)
return result
modules = ['os', 'sys', 'json', 'tkinter', 'unittest']
options = {'excludes': ['tkinter'], 'includes': ['json']}
print('В бандл попадут:', ', '.join(resolve_bundle(modules, options)))
В бандл попадут: os, sys, json, unittest
Модули, которых нет в списке анализа — например, импортируемые по вычисленному имени, — добавляются через includes. Тот же приём, что hiddenimports у PyInstaller: динамика невидима любому текстовому анализатору. Данные копируются включением в include_files:
import os, shutil
os.makedirs('src', exist_ok=True)
os.makedirs('build', exist_ok=True)
with open('src/config.json', 'w', encoding='utf-8') as f:
f.write('{"mode": "fast"}')
shutil.copyfile('src/config.json', 'build/config.json')
print('config рядом с exe:', os.path.isfile('build/config.json'))
with open('build/config.json', encoding='utf-8') as f:
print('Содержимое:', f.read())
shutil.rmtree('src')
shutil.rmtree('build')
config рядом с exe: True
Содержимое: {"mode": "fast"}Консоль или окно: цена GUI-режима
Параметр base у Executable решает, будет ли у приложения консоль. Заодно он решает, куда пойдёт вывод программы — потоки существуют только у консольного варианта:
def launch(base):
streams = ['stdin', 'stdout', 'stderr']
if base == 'Win32GUI':
return []
return streams
print('console:', ', '.join(launch('console')))
print('Win32GUI:', ', '.join(launch('Win32GUI')) or 'нет стандартных потоков')
console: stdin, stdout, stderr Win32GUI: нет стандартных потоков
В GUI-режиме print уходит в никуда, и падения невидимы: логирование в файл обязательно — то же правило, что и для --noconsole у PyInstaller.
Как упаковщик отличает stdlib от пакетов
Всем упаковщикам надо решить: что класть из стандартной библиотеки, а что копировать из окружения сборки. Граница описана в самом Python — списке имён stdlib:
import sys
candidates = ['os', 'json', 'sqlite3', 'requests', 'numpy']
for name in candidates:
if name in sys.stdlib_module_names:
kind = 'стандартная библиотека'
else:
kind = 'внешний пакет'
print(name, '-', kind)
os - стандартная библиотека json - стандартная библиотека sqlite3 - стандартная библиотека requests - внешний пакет numpy - внешний пакет
Стандартная библиотека приходит из рантайма бандла; внешние пакеты упаковщик копирует из окружения, в котором шла сборка. Поэтому окружение сборки делают чистым: устанавливают только то, что нужно приложению, иначе бандл разрастается чужими зависимостями.
Сравнение трёх упаковщиков
| Критерий | PyInstaller | py2exe | cx_Freeze |
|---|---|---|---|
| Платформы | Windows, macOS, Linux | Только Windows | Windows, Linux (macOS с оговорками) |
| Рецепт сборки | Команда плюс spec-файл | setup.py | setup.py |
| Windows-службы | Нет | Да | Нет |
| Готовые хуки библиотек | Много | Меньше | Средне |
| Сообщество | Крупнейшее | Скромное | Среднее |
Упаковщики разобраны все. Следующий урок — про то, что видно снаружи: декомпиляция pyc показывает, почему упаковка не защищает код и что именно восстанавливают декомпиляторы из байткода.
Сначала предскажи ответ в голове — это главный навык программиста.
def resolve_bundle(modules, options):
excluded = set(options.get('excludes', []))
included = sorted(options.get('includes', []))
result = []
for name in modules:
if name in excluded:
continue
result.append(name)
for name in included:
if name not in result:
result.append(name)
return result
modules = ['os', 'sys', 're']
options = {'includes': ['yaml']}
print(', '.join(resolve_bundle(modules, options)))
def launch(base):
streams = ['stdin', 'stdout', 'stderr']
if base == 'Win32GUI':
return []
return streams
print(len(launch('console')), len(launch('Win32GUI')))
import sys
for name in ('re', 'flask'):
if name in sys.stdlib_module_names:
kind = 'из рантайма'
else:
kind = 'копируется из окружения'
print(name, '-', kind)
1. Что общего у бандлов PyInstaller, py2exe и cx_Freeze?
2. Где живут рецепты сборки py2exe и cx_Freeze?
3. На каких платформах работает py2exe?
4. Что делает опция excludes в рецепте cx_Freeze?
5. Что меняет base='Win32GUI' у Executable?
6. Каким будет Python внутри бандла, собранного cx_Freeze?
7. Как в cx_Freeze добавить модуль, невидимый текстовому анализу?
Собери бандлы двумя упаковщиками: функция build(packager) создаёт zip-файл <packager>.exe с runtime.bin и точкой входа __main__.py. Проверь, что содержимое бандлов py2exe и cx_freeze совпадает, и выведи отсортированный список. Ожидаемый вывод: True и список.
Что такое py2exe и когда его стоит выбрать?
Старейший упаковщик Python-приложений для Windows: собирает exe из setup.py-рецепта. Выбор оправдан, когда целевая платформа строго Windows и нужны редкие возможности вроде сборки Windows-служб. В остальных случаях проще PyInstaller.
Урок 10: PyInstaller — сборка exeЧем cx_Freeze отличается от PyInstaller?
Рецептом и платформами: cx_Freeze описывает сборку декларативно в setup.py и собирает под Windows и Linux из одного рецепта; PyInstaller — командной строкой и spec-файлом, с самым большим набором готовых хуков под сторонние библиотеки.
Почему у py2exe и cx_Freeze рецепт setup.py, а не команды?
Оба выросли из инфраструктуры сборки библиотек, где setup.py — стандарт. Декларативный рецепт хранится в репозитории вместе с кодом, версионируется и повторяется командой одной строкой — это удобно для долгоживущих проектов.
Какой упаковщик выбрать по умолчанию?
PyInstaller: крупнейшее сообщество, готовые хуки под сотни библиотек, сборка одной командой. Альтернативы — для ниш: службы Windows и строгий Windows — py2exe; один рецепт под Windows и Linux — cx_Freeze; скорость исполнения — компилятор Nuitka.
Урок 12: Nuitka — компиляция в CСкрывает ли упаковщик мой код?
Нет: внутри любого из них — байткод, из которого декомпиляторы восстанавливают читаемый исходник. Упаковка решает проблему распространения, не защиты. Сокрытие даёт нативная компиляция в машинный код.
Урок 15: декомпиляция pycПонравился урок? Сошлитесь на него
«Разница упаковщиков не в содержимом бандла — внутри у всех интерпретатор, байткод и данные, — а в рецепте сборки и удобстве сопровождения.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Компиляция · Урок 10
PyInstaller: сборка exe из Python-скрипта
От скрипта к exe: PyInstaller анализирует импорты, кладёт интерпретатор, байткод и данные в один файл. Собираем бандл по шагам и проверяем его на чистой машине.
Компиляция · Урок 12
Nuitka: компиляция Python-кода в C
От упаковки к компиляции: как Nuitka переводит программу в C, что становится машинным кодом, а что остаётся PyObject — и где ждать ускорения.
Компиляция · Урок 15
Декомпиляция pyc в Python: что видно из байткода
pyc не содержит исходного текста, но хранит имена, константы и структуру. Разбираем, что восстановит декомпилятор, что потеряно навсегда и где проходит этическая граница.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
pytest · Урок 6
yield-фикстуры: уборка после теста (setup и teardown)
Тест создал файл — и кто-то должен убрать за ним. yield-фикстура держит подготовку и уборку в одной функции, а уборку выполняет всегда, даже когда тест упал.
pytest setup и teardown
SQLAlchemy · Урок 18
PostgreSQL после SQLite: что меняется
Хорошая новость урока: код не меняется. Меняется одна строка — и всё остальное важно знать, чтобы эта строка правда оказалась единственной.
sqlalchemy отличие sqlite postgresql
Компиляция · Урок 11
PyInstaller onefile против onedir: что выбрать
Один exe или exe с папкой _internal: как onefile и onedir влияют на старт, обновление, антивирусы и поиск файлов данных — с механикой бутстрапа на живом коде.
pyinstaller onefilepyinstaller onedir