Создание запускаемых файлов из скриптов на языке Python с помощью cx_Freeze

Немного рекламы

Содержание

  1. Введение
  2. Установка
  3. Первый пример
  4. Добавление файлов Microsoft Visual C++ Redistributable
  5. Избавляемся от лишних модулей
  6. Наводим красоту в сборке
  7. Добавление модулей в сборку
  8. Сборка нескольких запускаемых файлов
  9. Добавление дополнительных файлов в сборку
  10. Создание приложений с графическим интерфейсом
  11. Установка иконки приложения
  12. Создание инсталятора MSI
  13. Скрипт cxfreeze-quickstart для создания файла setup.py
  14. Заключение

1. Введение

Python - отличный язык программирования для широкого круга задач, начиная от автоматизации рутинных действий до создания web- или настольных приложений с графическим интерфейсом. Но Python - интерпретируемый язык, а это значит, что если вы хотите распространять ваше приложение, то у пользователя должен быть установлен интерпретатор, на вход которого он должен подать исходные коды вашей программы. Если вы разрабатываете приложение под Linux или основная масса пользователей вашей программы - айтишники (программисты, админы и им сочувствующие), то требование установить интерпретатор Python скорее всего их не испугает. А вот заставлять устанавливать Python, а, возможно, еще и дополнительные библиотеки обычных пользователей - сомнительная идея.

Однако, Python хорош еще тем, что ядро интерпретатора достаточно компактное, оно умещается в единственную dll-ку размером 3.5 МБ + дополнительные модули. Это позволяет сделать такой хак: скопируем в отдельную папку файл PythonXX.dll (где XX - номер версии интерпретатора Python) и также скомпилированные файлы *.pyc, созданные из исходных файлов вашего приложения и используемых модулей, а затем рядом с этими файлами создадим запускаемый файл .exe, который будет подменять и перенаправлять вызовы интерпретатора в скопированную dll-ку (это очень грубое представление работы создаваемого файла .exe, но для простоты оставим так). Набор этих файлов можно передавать конечному пользователю, он запустит запускаемый файл и даже не будет догадываться, что "под капотом" работает интерпретатор Python.

Именно это и делает библиотека cx_Freeze, причем такой прием эта библиотека умеет применять для Windows, Linux и Mac OS. Эта статья будет посвящена использованию данной библиотеки. На простейших примерах мы рассмотрим основы работы с этой библиотекой cx_Freeze и различные сценарии ее использования.

Все примеры в этой статье тестировались на Python 3.6 и cx_Freeze 5.0.2. cx_Freeze пока еще умеет работать с Python 2.7, но судя по коммитам на github, следующая версия cx_Freeze 6 будет работать только с Python 3.5 и выше.

2. Установка

Устанавливается cx_Freeze стандартно через pip. Если вы хотите установить cx_Freeze глобально для всех пользователей системы, то нужно выполнить в консоли команду

pip install cx_freeze

Разумеется, для этого у вас должны быть права администратора, и консоль должна быть запущена от администратора.

Или вы можете установить cx_Freeze только для вашего пользователя, тогда админские права не понадобятся:

pip install cx_freeze --user

В реальном проекте идеологически более верно установить cx_Freeze внутри virtualenv, но в этом случае могут быть некоторые проблемы (например).

3. Первый пример

Теперь, когда cx_Freeze установлен, можно приступать к изучению возможностей этой библиотеки. Все примеры из статьи вы можете найти на github. Примеры последовательно пронумерованы в том порядке, как они описаны в статье. Приложение, которое мы будем упаковывать в запускаемые файлы, будет называться example.py.

Для начала создадим простейший python-скрипт с именем example.py, который не импортирует никакие модули (Example 01 на github):

# example.py

# coding: utf-8


if __name__ == '__main__':
    print('Hello world!')

Скорее всего нет смысла описывать, что делает данный скрипт. :) Желающие убедиться, что скрипт работает, могут выполнить команду:

python example.py

Если у вас установлено несколько версий Python, то под Windows можно воспользоваться утилитой py и явно указать номер версии интерпретатора:

py -3.6 example.py

или

py -3 example.py

Давайте упакуем наш очень полезный скрипт в запускаемый файл, который можно будет передать пользователю. Для этого создадим файл setup.py со следующим содержимым:

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py')]

setup(name='hello_world',
      version='0.0.1',
      description='My Hello World App!',
      executables=executables)

Имя скрипта для сборки может быть произвольным, но обычно принято называть егоsetup.py, и мы будем придерживаться этого правила. Те, кто создавал Python-пакеты с помощью утилит distutils или setuptools, увидят в работе с cx_Freeze что-то знакомое.

Итак, в файле setup.py мы импортируем из модуля cx_Freeze функцию setup и класс Executable. Самое главное здесь - это функция setup, в нее передаются все параметры, которые описывают сборку. В параметрах функции setup мы указываем имя приложения (параметр name), номер версии (параметр version), краткое описание приложения (параметр description), а также параметр executables, который должен быть списком экземпляров класса Executable. Почему тут должен быть именно список, и не достаточно одного экземпляра класса Executable будет описано чуть позже в разделе Сборка нескольких запускаемых файлов.

Конструктор класса Executable может принимать достаточно большое количество параметров. Обязательным параметром является только имя запускаемого python-файла. Если ваша программа состоит из большого количества скриптов, то указывать нужно на тот файл, который запускал бы пользователь, если бы он запускал приложение из исходников. Полностью конструктор класса Executable выглядит следующим образом (класс описан в файле freezer.py исходников cx_Freeze):

class Executable(object):
    def __init__(self, script, initScript = None, base = None,
            targetName = None, icon = None, shortcutName = None,
            shortcutDir = None, copyright = None, trademarks = None):

Некоторыми из этих параметров мы еще воспользуемся, а некоторые останутся за рамками статьи, назначение многих из них понятно из названия переменных.

Чтобы создать запускаемый файл, запустим консоль и перейдем в папку, где лежит файл setup.py. Затем нужно выполнить команду

python setup.py build

На экран будет выведено достаточно много теста:

running build
running build_exe
copying C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\cx_Freeze\bases\Console.exe -> build\exe.win32-3.6\example_01.exe
copying C:\Program Files (x86)\Python36-32\python36.dll -> build\exe.win32-3.6\python36.dll
*** WARNING *** unable to create version resource
install pywin32 extensions first
writing zip file build\exe.win32-3.6\python36.zip

  Name                      File
  ----                      ----
m BUILD_CONSTANTS
m __future__                C:\Program Files (x86)\Python36-32\lib\__future__.py
m __startup__               C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\cx_Freeze\initscripts\__startup__.py
m _ast

...

m xml.parsers.expat         C:\Program Files (x86)\Python36-32\lib\xml\parsers\expat.py
m zipfile                   C:\Program Files (x86)\Python36-32\lib\zipfile.py
m zipimport
m zlib

Missing modules:
? __main__ imported from bdb, pdb
? _dummy_threading imported from dummy_threading
? _frozen_importlib imported from importlib, importlib.abc
? _frozen_importlib_external imported from importlib, importlib._bootstrap, importlib.abc
? _posixsubprocess imported from subprocess
? _winreg imported from platform
? grp imported from shutil, tarfile
? java.lang imported from platform
? org.python.core imported from copy, pickle
? os.path imported from os, pkgutil, py_compile, tracemalloc, unittest, unittest.util
? posix imported from os
? pwd imported from http.server, posixpath, shutil, tarfile, webbrowser
? termios imported from tty
? vms_lib imported from platform
This is not necessarily a problem - the modules may not be needed on this platform.

copying C:\Program Files (x86)\Python36-32\DLLs\_bz2.pyd -> build\exe.win32-3.6\_bz2.pyd
copying C:\Program Files (x86)\Python36-32\DLLs\_hashlib.pyd -> build\exe.win32-3.6\_hashlib.pyd
copying C:\Program Files (x86)\Python36-32\DLLs\_lzma.pyd -> build\exe.win32-3.6\_lzma.pyd
copying C:\Program Files (x86)\Python36-32\DLLs\_socket.pyd -> build\exe.win32-3.6\_socket.pyd
copying C:\Program Files (x86)\Python36-32\DLLs\_ssl.pyd -> build\exe.win32-3.6\_ssl.pyd
copying C:\Program Files (x86)\Python36-32\DLLs\pyexpat.pyd -> build\exe.win32-3.6\pyexpat.pyd
copying C:\Program Files (x86)\Python36-32\DLLs\select.pyd -> build\exe.win32-3.6\select.pyd
copying C:\Program Files (x86)\Python36-32\DLLs\unicodedata.pyd -> build\exe.win32-3.6\unicodedata.pyd

В довольно большом логе работы можно увидеть, какие модули Python попали в сборку, а каких по мнению cx_Freeze не хватает. Чаще всего на этот вывод можно не обращать внимания, если нет каких-либо ошибок.

После запуска скрипта сборки, если все прошло успешно (если нет сообщений об ошибках), рядом с файлами example.py и setup.py должна появиться папка build, а в ней папка, имя которой зависит от версии Python. Например, если сборка запускалась с помощью 32-битной версии интерпретатора Python 3.6, то будет создана папка exe.win32-3.6, а в ней огромное количество файлов и папок.

Среди файлов можно увидеть файл example.exe - файл, который будут запускать пользователи. Попробуйте его запустить - откроется консольное окно, в котором промелькнет фраза "Hello world!"

Рядом с запускаемым файлом расположился файл с ядром интерпретатора Python - python36.dll, а также файл python36.zip - архив, в котором хранятся модули Python, скомпилированные в файлы *.pyc. Также в корне находятся динамические загружаемые библиотеки *.pyd и папки с пакетами, которые не попали в файл python36.zip. Если открыть этот архив, то можно увидеть, что он содержит файлы *.pyc.

По умолчанию cx_Freeze в архив помещает одиночные модули, а пакеты помещает в корень папки сборки. В данный момент в сборку попало очень много лишних модулей, который на самом деле не используются в программе. Например, среди модулей можно увидеть unittest, logging, xml, urllib и другие, которые явно не используются в нашей простой программе. Скоро мы научимся удалять лишние модули из сборки и вручную добавлять необходимые.

4. Добавление файлов Microsoft Visual C++ Redistributable

Интерпретатор Python компилируется под Windows с помощью Microsoft Visual C++, поэтому для того, чтобы пользователи могли запускать собранное вами приложение, у них должен установлен Microsoft Visual C++ Redistributable Package, который они должны скачать с сайта Microsoft. Это не очень удобно. Ситуация усугубляется тем, что разные версии Python компилируются с помощью разных версий компилятора.

Чтобы решить эту проблему вы можете поставлять с вашим дистрибутивом необходимые файлы *.dll. Например, для Python 3.6 достаточно скопировать файл vcruntime140.dll из папки с установленным Python. Для того, чтобы не делать эту вручную, cx_Freeze может делать это за вас, для этого надо добавить новый для нас параметр в функцию setup скрипта сборки setup.py. Исправленный скрипт сборки теперь будет выглядеть следующим образом (Example 02):

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py')]

options = {
    'build_exe': {
        'include_msvcr': True,
    }
}

setup(name='hello_world',
      version='0.0.2',
      description='My Hello World App!',
      executables=executables,
      options=options)

В функцию setup был добавлен новый параметр options, который должен быть словарем. Этот словарь содержит вложенные словари, описывающие особенности сборки для разных операционных систем и типов сборки (cx_Freeze кроме запускаемых файлов может создавать инсталяторы). Все возможные параметры можно найти в документации, некоторые из этих параметров мы еще будем использовать в дальнейшем.

Пока мы добавили единственный параметр в словарь build_exe - 'include_msvcr': True. Этот параметр говорит cx_Freeze, что нужно скопировать необходимые файлы *.dll из Microsoft Visual C++ Redistributable.

Но в последних версиях cx_Freeze есть некоторые непонятные моменты, связанные с этим параметром. По идее после запуска нового скрипта сборки с помощью команды

python setup.py build

должна создаться та же самая сборка, что и в предыдущем примере, но только в папке с программой должен появиться файл vcruntime140.dll (для Python 3.6). Во время написания этой статьи (напомню, что для тестирования примеров использовался cx_Freeze 5.0.2) этот файл не копировался. На github есть несколько обсуждений (#275), где говорится, что этот файл не копируется при использовании virtualenv. Я пробовал запускать эти примеры и без virtualenv, файл vcruntime140.dll все равно не копировался. В одном из следующих примеров, когда мы будем собирать скрипт, создающий графический интерфейс с помощью библиотеки wxPython, этот файл появится. С чем связано такое поведение - пока загадка. Но при создании своих сборок имейте это в виду, может быть нужно будет копировать файлы Microsoft Visual C++ Redistributable вручную. В дальнейших примерах для общности везде будет добавлен параметр include_msvcr.

5. Избавляемся от лишних модулей

Как мы видим, несмотря на то, что наш исходный скрипт Hello World явно не импортирует никакие модули, в сборку попало достаточно много файлов *.pyc и *.pyd. Некоторые из них нам явно не нужны (например, unittest, logging и другие). Полный размер сборки сейчас составляет 9.05 МБ, что для простого Hello World кажется излишним, хотя при современных размерах жестких дисков скорее всего не будет проблемой.

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

Чтобы исключить какие-либо модули из сборки, в словарь параметров build_exe нужно добавить ключ excludes, значением которого должен быть список строк с именами модулей, которые нужно исключить. Давайте для начала исключим модули, которые нам явно не нужны. Добавим параметр excludes с небольшим списком модулей (Example 03):

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py')]

excludes = ['unicodedata', 'logging', 'unittest', 'email', 'html', 'http', 'urllib',
            'xml', 'bz2']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
    }
}

setup(name='hello_world',
      version='0.0.3',
      description='My Hello World App!',
      executables=executables,
      options=options)

После сборки заходим в папку build\exe.win32-3.6\ или аналогичную ей и видим, что количество файлов и папок заметно уменьшилось:

В архиве python36.zip количество файлов тоже немного уменьшилось:

Общий размер сборки теперь составляет 5.93 МБ - размер уже уменьшился на треть. Обязательно надо убедиться, что программа работает.

В реальном проекте, возможно, стоит на этом остановиться и не рисковать сломать программу, удаляя более мелкие модули. Но поскольку у нас очень простая программа, мы можем пойти дальше и путем перебора попробовать удалить как можно больше ненужных модулей. Через несколько (десятков?) итераций удаления и восстановления модулей получился следующий скрипт сборки (Example 04):

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py')]

excludes = ['unicodedata', 'logging', 'unittest', 'email', 'html', 'http', 'urllib',
            'xml', 'pydoc', 'doctest', 'argparse', 'datetime', 'zipfile',
            'subprocess', 'pickle', 'threading', 'locale', 'calendar', 'functools',
            'weakref', 'tokenize', 'base64', 'gettext', 'heapq', 're', 'operator',
            'bz2', 'fnmatch', 'getopt', 'reprlib', 'string', 'stringprep',
            'contextlib', 'quopri', 'copy', 'imp', 'keyword', 'linecache']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
    }
}

setup(name='hello_world',
      version='0.0.4',
      description='My Hello World App!',
      executables=executables,
      options=options)

После его запуска с помощью команды python setup.py build сборка будет выглядеть следующим образом:

А в архиве python36.zip остались лишь следующие модули:

В архиве осталось всего 19 файлов из 100 первоначальных. Теперь сборка занимает 3.7 МБ. По-моему, неплохо. Но это еще не все.

Мы видим, что часть модулей находится в архиве python36.zip , а часть в отдельных папках в папке сборки. Это может быть неприятно из эстетических соображений, если вы не хотите видеть лишние служебные файлы и папки в сборке. На этот случай у cx_Freeze есть еще один параметр, который указывает, какие модули он должен обязательно помещать в архив. Но имейте в виду, что распаковка архива при работе программы тоже будет занимать время, поэтому запуск программы с заархивированными модулями будет медленнее. Насколько медленнее - надо измерять, возможно, скорость будет не заметна для пользователей (скорее всего так и будет).

Чтобы указать, какие модули нужно обязательно поместить в архив, используется элемент с именем zip_include_packages словаря параметров build_exe . Этот элемент словаря должен содержать список строк с именами модулей (пакетов), которые нужно поместить в архив. В нашем случае скрипт сборки преобразуется к следующему виду (Example 05):

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py')]

excludes = ['unicodedata', 'logging', 'unittest', 'email', 'html', 'http', 'urllib',
            'xml', 'pydoc', 'doctest', 'argparse', 'datetime', 'zipfile',
            'subprocess', 'pickle', 'threading', 'locale', 'calendar', 'functools',
            'weakref', 'tokenize', 'base64', 'gettext', 'heapq', 're', 'operator',
            'bz2', 'fnmatch', 'getopt', 'reprlib', 'string', 'stringprep',
            'contextlib', 'quopri', 'copy', 'imp', 'keyword', 'linecache']

zip_include_packages = ['collections', 'encodings', 'importlib']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'zip_include_packages': zip_include_packages,
    }
}

setup(name='hello_world',
      version='0.0.5',
      description='My Hello World App!',
      executables=executables,
      options=options)

Запускаем сборку и смотрим на ее результат:

Осталось всего лишь три файла!

Указанные в скрипте сборки модули переместились в файл python36.zip:

Теперь сборка занимает всего лишь 3.45 МБ - по сравнению с исходными 9 МБ размер сократился почти в 3 раза. Еще раз напомню, что в сборку еще не были включены файлы из Microsoft Visual C++ Redistributable, которые почему-то не были помещены автоматически библиотекой cx_Freeze. Ну и, конечно, в этом примере было применено очень агрессивное исключение модулей, которые могут использоваться во многих других модулях. Обычно этого делать не стоит, чтобы не получить неожиданную ошибку. К тому же многие файлы *.pyc занимают всего лишь десятки кБ, из-за которых нет смысла рисковать.

6. Наводим красоту в сборке

Мы создали запускаемую сборку нашей программы. По умолчанию она помещается в папку с именем вроде build\exe.win32-3.6\ , имя которой зависит от версии Python, для которой происходила сборка. Во многих случаях можно на этом остановиться, но в больших проектах создание такой сборки - это лишь один промежуточный этап автоматического создания дистрибутива. После этого полученную сборку, возможно, нужно будет отправить на сервер для тестирования, может быть нужно будет запустить локальные тесты или создать из сборки инсталятор. Поэтому может оказаться, что такое имя папки неудобно или нужно сохранить сборку на другом диске. Все это, конечно, можно сделать сторонними скриптами, но cx_Freeze позволяет настраивать, куда будет помещена полученная сборка.

Давайте изменим файл setup.py, таким образом, чтобы создаваемая сборка помещалась, например, в папку build_windows рядом со скриптами. Для этого снова нужно добавить новый параметр в словарь build_exe. Этот параметр тоже должен называться build_exe и хранить строку с папкой, куда должна помещаться сборка (Example 06):

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py')]

excludes = ['unicodedata', 'logging', 'unittest', 'email', 'html', 'http', 'urllib',
            'xml', 'pydoc', 'doctest', 'argparse', 'datetime', 'zipfile',
            'subprocess', 'pickle', 'threading', 'locale', 'calendar', 'functools',
            'weakref', 'tokenize', 'base64', 'gettext', 'heapq', 're', 'operator',
            'bz2', 'fnmatch', 'getopt', 'reprlib', 'string', 'stringprep',
            'contextlib', 'quopri', 'copy', 'imp', 'keyword', 'linecache']

zip_include_packages = ['collections', 'encodings', 'importlib']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'zip_include_packages': zip_include_packages,
        'build_exe': 'build_windows',
    }
}

setup(name='hello_world',
      version='0.0.6',
      description='My Hello World App!',
      executables=executables,
      options=options)

Теперь после создания сборки рядом с файлами example.py и setup.py появится папка build_windows, где будет находиться созданная сборка, ничем не отличающаяся от предыдущего примера.

По умолчанию cx_Freeze создает запускаемый файл, имя которого (без расширения) совпадает с именем запускаемого скрипта. То есть в нашем случае - example.exe, поскольку наш запускаемый сприпт называется example.py. Часто нужно дать запускаемому файлу другое имя. Сделать это тоже очень просто, достаточно добавить еще один параметр, но на этот раз в конструктор класса Executable. Имя нужного нам параметра - targetName. Это должна быть строка, которая содержит имя создаваемого запускаемого файла. Поэтому, если мы хотим, чтобы cx_Freeze создал запускаемый файл hello_world.exe, то предыдущий файл setup.py нужно изменить следующим образом (Example 07):

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py', targetName='hello_world.exe')]

excludes = ['unicodedata', 'logging', 'unittest', 'email', 'html', 'http', 'urllib',
            'xml', 'pydoc', 'doctest', 'argparse', 'datetime', 'zipfile',
            'subprocess', 'pickle', 'threading', 'locale', 'calendar', 'functools',
            'weakref', 'tokenize', 'base64', 'gettext', 'heapq', 're', 'operator',
            'bz2', 'fnmatch', 'getopt', 'reprlib', 'string', 'stringprep',
            'contextlib', 'quopri', 'copy', 'imp', 'keyword', 'linecache']

zip_include_packages = ['collections', 'encodings', 'importlib']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'zip_include_packages': zip_include_packages,
        'build_exe': 'build_windows',
    }
}

setup(name='hello_world',
      version='0.0.7',
      description='My Hello World App!',
      executables=executables,
      options=options)

Запускаем сборку и видим, что в папке build_windows вместо example.exe появился файл hello_world.exe:

7. Добавление модулей в сборку

До сих пор мы только исключали лишние модули из создаваемой сборки с помощью параметра excludes словаря build_exe. Однако, импорт модулей в Python может происходить не только с помощью оператора import, но и динамически по имени модуля с помощью функции '_import_', а может быть импорт будет осуществляться с использованием сложной логики, когда cx_Freeze не сможет понять, что нужно добавить какой-то модуль в сборку.

Давайте изменим наш пример таким образом, чтобы он импортировал модуль json таким образом, что cx_Freeze об этом не узнает (Example 08):

# coding: utf-8


if __name__ == '__main__':
    module = __import__('json')
    print('Hello world!')

Скрипт сборки setup.py пока останется неизменным. Запустим сборку с помощью команды python setup.py build, ошибок при этом не возникнет, будет создана сборка в папке build_windows.

Однако если теперь мы запустим созданный файл hello_world.exe, то в консоль будет выведено информация об исключении:

> hello_world.exe

Traceback (most recent call last):
  File "C:\...\Python36\site-packages\cx_Freeze\initscripts\__startup__.py", line 14, in run
    module.run()
  File "C:\...\Python36\site-packages\cx_Freeze\initscripts\Console.py", line 26, in run
    exec(code, m.__dict__)
  File "example.py", line 5, in <module>
ModuleNotFoundError: No module named 'json'

На указанные пути обращать внимания не будем. Для нас сейчас главное, что мы получили ошибку времени исполнения. Надо ее исправлять, а именно нужно добавить модуль json в сборку.

Если для исключения модулей из сборку предназначен параметр excludes, до для включения модуля в сборке используется параметр includes, который тоже должен содержать список модулей, которые нужно добавить в сборку. Параметр includes так же должен входить в словарь build_exe. Добавим модуль json в этот список (Example 08-1):

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py', targetName='hello_world.exe')]

excludes = ['unicodedata', 'logging', 'unittest', 'email', 'html', 'http', 'urllib',
            'xml', 'pydoc', 'doctest', 'argparse', 'datetime', 'zipfile',
            'subprocess', 'pickle', 'threading', 'locale', 'calendar', 'functools',
            'weakref', 'tokenize', 'base64', 'gettext', 'heapq', 're', 'operator',
            'bz2', 'fnmatch', 'getopt', 'reprlib', 'string', 'stringprep',
            'contextlib', 'quopri', 'copy', 'imp', 'keyword', 'linecache']

includes = ['json']

zip_include_packages = ['collections', 'encodings', 'importlib']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'includes': includes,
        'zip_include_packages': zip_include_packages,
        'build_exe': 'build_windows',
    }
}

setup(name='hello_world',
      version='0.0.8',
      description='My Hello World App!',
      executables=executables,
      options=options)

Создаем сборку, заходим в папку build_windows и видим, что в ней появилась папка с именем json:

Но не спешим радоваться. При попытке запустить hello_world.exe мы получим новую ошибку:

> hello_world.exe
Traceback (most recent call last):
  File "C:\...\Python36\site-packages\cx_Freeze\initscripts\__startup__.py", line 14, in run
    module.run()
  File "C:\...\Python36\site-packages\cx_Freeze\initscripts\Console.py", line 26, in run
    exec(code, m.__dict__)
  File "example.py", line 5, in <module>
  File "C:\Program Files (x86)\Python36-32\lib\json\__init__.py", line 106, in <module>
    from .decoder import JSONDecoder, JSONDecodeError
  File "C:\Program Files (x86)\Python36-32\lib\json\decoder.py", line 3, in <module>
    import re
ModuleNotFoundError: No module named 're'

Теперь не хватает модуля для работы с регулярными выражениями re. Если мы посмотрим внимательнее на наш предыдущий файл setup.py, то увидим, что этот модуль мы исключили из сборки с помощью параметра excludes, а этот модуль, оказывается, используется в модуле json. Если мы исключим модуль re из списка excludes, то это тоже не решит проблему, окажется, что еще нужны другие модули, которые вы выкинули из сборки. Я же предупреждал, что не надо увлекаться удалением модулей. Постепенно удаляя имена модулей из списка excludes можно добиться работоспособной сборки. Новый файл setup.py теперь выглядит следующим образом (Example 08-2):

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py', targetName='hello_world.exe')]

excludes = ['unicodedata', 'logging', 'unittest', 'email', 'html', 'http', 'urllib',
            'xml', 'pydoc', 'doctest', 'argparse', 'datetime', 'zipfile',
            'subprocess', 'pickle', 'threading', 'locale', 'calendar',
            'tokenize', 'base64', 'gettext',
            'bz2', 'fnmatch', 'getopt', 'string', 'stringprep',
            'contextlib', 'quopri', 'copy', 'imp', 'linecache']

includes = ['json']

zip_include_packages = ['collections', 'encodings', 'importlib']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'includes': includes,
        'zip_include_packages': zip_include_packages,
        'build_exe': 'build_windows',
    }
}

setup(name='hello_world',
      version='0.0.8',
      description='My Hello World App!',
      executables=executables,
      options=options)

Из списка excludes были убраны следующие модули: re, functools, operator, keyword, heapq, reprlib, weakref.

Создаем сборку и получаем теперь уже работоспособное приложение. Если мы зайдем в папку build_windows, то увидим, что внешне ничего не изменилось, в ней из перечисленных модулей значится только папка json, остальные модули были помещены в архив python36.zip:

И для аккуратности сделаем так, чтобы модуль json тоже был помещен в архив python36.zip, добавим строку 'json' в параметр zip_include_packages (Example 08-3):

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py', targetName='hello_world.exe')]

excludes = ['unicodedata', 'logging', 'unittest', 'email', 'html', 'http', 'urllib',
            'xml', 'pydoc', 'doctest', 'argparse', 'datetime', 'zipfile',
            'subprocess', 'pickle', 'threading', 'locale', 'calendar',
            'tokenize', 'base64', 'gettext',
            'bz2', 'fnmatch', 'getopt', 'string', 'stringprep',
            'contextlib', 'quopri', 'copy', 'imp', 'linecache']

includes = ['json']

zip_include_packages = ['collections', 'encodings', 'importlib', 'json']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'includes': includes,
        'zip_include_packages': zip_include_packages,
        'build_exe': 'build_windows',
    }
}

setup(name='hello_world',
      version='0.0.8',
      description='My Hello World App!',
      executables=executables,
      options=options)

Теперь сборка выглядит аккуратно и состоит всего из трех файлов:

А в архиве python36.zip появился модуль json:

8. Сборка нескольких запускаемых файлов

Давайте вернемся к вопросу о том, зачем в качестве параметра executables функции setup должен быть передан список экземпляров класса Executable, а не единственный экземпляр класса. Это полезно в тех случаях, когда ваше приложение состоит из нескольких запускаемых файлов. Например, это может быть основная программа и дополнительная программа для обновления основной программы, или может быть ваше приложение - это набор небольших утилит, написанных на Python. В этом случае для каждой такой программы надо делать отдельную сборку, но как быть с архивом модулей (python36.zip или аналогичного), ведь у каждой программы будет свой список импортируемых модулей? Именно поэтому cx_Freeze позволяет указывать список создаваемых запускаемых файлов, а в архив модулей будут помещены все необходимые модули для всех приложений.

Сделаем два простых скрипта в одной папке (Example 09).

hello_01.py:

# coding: utf-8

import json


if __name__ == '__main__':
    print('Hello world 01!')

hello_02.py:

# coding: utf-8


if __name__ == '__main__':
    print('Hello world 02!')

Скрипт hello_01.py явно импортирует модуль json, поэтому в файле setup.py мы не будем использовать параметр includes - cx_Freeze сам найдет все необходимые модули. Для скрипта hello_02.py модуль json не требуется. Для каждого из этих модулей мы должны создать свой экземпляр класса Executable в setup.py, а остальное нам уже знакомо:

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('hello_01.py', targetName='hello_world_01.exe'),
               Executable('hello_02.py', targetName='hello_world_02.exe'),
               ]

excludes = ['unicodedata', 'logging', 'unittest', 'email', 'html', 'http', 'urllib',
            'xml', 'pydoc', 'doctest', 'argparse', 'datetime', 'zipfile',
            'subprocess', 'pickle', 'threading', 'locale', 'calendar',
            'tokenize', 'base64', 'gettext',
            'bz2', 'fnmatch', 'getopt', 'string', 'stringprep',
            'contextlib', 'quopri', 'copy', 'imp', 'linecache']

zip_include_packages = ['collections', 'encodings', 'importlib', 'json']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'zip_include_packages': zip_include_packages,
        'build_exe': 'build_windows',
    }
}

setup(name='hello_world',
      version='0.0.9',
      description='My Hello World App!',
      executables=executables,
      options=options)

Запускаем сборку и видим, что в папке build_windows появились файлы hello_world_01.exe и hello_world_02.exe:

А в архиве python36.zip имеется модуль json, который нужен только для hello_world_01.exe:

9. Добавление дополнительных файлов в сборку

До сих пор у нас приложение состояло только из скриптов Python, но часто к скриптам должны прилагаться какие-нибудь файлы с данными. Давайте дополним один из предыдущих примеров, сделаем так, чтобы в отдельной папке с именем data хранился файл data.json с данными в формате JSON о том, кого программа должна приветствовать (Example 10):

{
        "first_name": "John",
        "last_name": "Zoidberg"
}

Т.е. общая структура файлов будет такой:

.
│--example.py
│--setup.py

└───data
        data.json

Скрипт example.py теперь выглядит так:

# coding: utf-8

import json


if __name__ == '__main__':
    fname = 'data/data.json'
    with open(fname) as fp:
        obj = json.load(fp)

    print('Hello {} {}!'.format(obj['first_name'], obj['last_name']))

Если мы воспользуемся одним из предыдущих скриптов setup.py и запустим полученный файл .exe, то мы получим ошибку из-за того, что папка data не будет скопирована в папку со сборкой. На этот случай у cx_Freeze есть еще один параметр include_files, который тоже должен входить в словарь build_exe и содержать либо список, в который могут входить либо строковые элементы с именами файлов или папок, которые нужно добавить к сборке, либо кортежи, показывающие, как исходные файлы должны быть переименованы (об этом мы поговорим чуть позже).

Пока сделаем, чтобы папка data была добавлена к сборке.

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py', targetName='hello_world.exe')]

excludes = ['unicodedata', 'logging', 'unittest', 'email', 'html', 'http', 'urllib',
            'xml', 'pydoc', 'doctest', 'argparse', 'datetime', 'zipfile',
            'subprocess', 'pickle', 'threading', 'locale', 'calendar',
            'tokenize', 'base64', 'gettext',
            'bz2', 'fnmatch', 'getopt', 'string', 'stringprep',
            'contextlib', 'quopri', 'copy', 'imp', 'linecache']

zip_include_packages = ['collections', 'encodings', 'importlib', 'json']

include_files = ['data']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'zip_include_packages': zip_include_packages,
        'build_exe': 'build_windows',
        'include_files': include_files,
    }
}

setup(name='hello_world',
      version='0.0.10',
      description='My Hello World App!',
      executables=executables,
      options=options)

После удачной сборки папка build_windows будет выглядеть следующим образом:

И приложение hello_world.exe при запуске поприветствует доктора Зойдберга (пока не залез в Википедию, не знал, что его зовут Джон).

Давайте немного усложним задачу. Пусть исходники у нас включают в себя файлы readme.txt и documentation.txt, которые не используются в скрипте (он останется прежним), но эти файлы хотелось бы добавить в сборку, при этом файл documentation.txt нужно поместить в отдельную папку doc и переименовать в doc.txt (Example 11).

Для этого воспользуемся тем, что в список include_files можно включать не только строки, но и кортежи из двух элементов. Первый элемент кортежа должен быть строкой с именем исходного файла, а второй элемент - именем этого файла в папке сборки, при этом исходный файл можно помещать в поддиректории.

Наш новый файл setup.py теерь выглядит так:

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py', targetName='hello_world.exe')]

excludes = ['unicodedata', 'logging', 'unittest', 'email', 'html', 'http', 'urllib',
            'xml', 'pydoc', 'doctest', 'argparse', 'datetime', 'zipfile',
            'subprocess', 'pickle', 'threading', 'locale', 'calendar',
            'tokenize', 'base64', 'gettext',
            'bz2', 'fnmatch', 'getopt', 'string', 'stringprep',
            'contextlib', 'quopri', 'copy', 'imp', 'linecache']

includes = ['json']

zip_include_packages = ['collections', 'encodings', 'importlib', 'json']

include_files = ['data',
                 'readme.txt',
                 ('documentation.txt', 'doc/doc.txt'),
                 ]

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'includes': includes,
        'zip_include_packages': zip_include_packages,
        'build_exe': 'build_windows',
        'include_files': include_files,
    }
}

setup(name='hello_world',
      version='0.0.11',
      description='My Hello World App!',
      executables=executables,
      options=options)

Запускаем сборку и убеждаемся, что новые файлы были скопированы в папку сборки:

Это очень полезная возможность при создании сборок.

10. Создание приложений с графическим интерфейсом

До сих пор мы создавали запускаемые файлы для консольных приложений, но если вы разрабатываете программу для обычных пользователей, то им нужен графический интерфейс (GUI). Для создания графического интерфейса на Python существует много библиотек, которые в основном являются оболочками над библиотеками, написанными на C или C++. Наиболее известные из них - Tkinter (входит в стандартную библиотеку Python), PyQt, PySide, wxPython и др. В этой статье для примера рассмотрим использование библиотеки wxPython, которая является оболочкой поверх библиотеки wxWidgets. В примерах будет использоваться wxPython 4.0, которая на момент написания статьи имеет статус пока еще альфа-версии, зато в отличие от предыдущих версий может устанавливаться из PyPi с помощью pip.

Устанавливается wxPython 4.0 обычным способом:

pip install wxpython

или, если вы хотите установить wxPython только для своего пользователя (в этом случае не требуются права администратора):

pip install wxpython --user

Сделаем простейшее приложение, которое создает пустое окно с заголовком "Hello wxPython". Поскольку данная статья не является учебником по wxPython, то не будем подробно останавливаться на использовании этой библиотеки, хотя я думаю, что код такого простого приложения достаточно понятен (Example 12).

# example.py

# coding: utf-8

import wx


class MyApp(wx.App):
    def OnInit(self):
        self.mainWnd = wx.Frame(None, -1, "")
        self.SetTopWindow(self.mainWnd)
        self.mainWnd.SetTitle('Hello wxPython')
        self.mainWnd.Show()
        return True


if __name__ == "__main__":
    app = MyApp(False)
    app.MainLoop()

Создадим скрипт сборки setup.py, в котором пока тоже нет ничего необычного:

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py', targetName='hello_wx.exe')]

excludes = ['logging', 'unittest', 'email', 'html', 'http', 'urllib', 'xml',
            'unicodedata', 'bz2', 'select']

zip_include_packages = ['collections', 'encodings', 'importlib', 'wx']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'zip_include_packages': zip_include_packages,
        'build_exe': 'build_windows',
    }
}

setup(name='hello_world',
      version='0.0.12',
      description='My Hello World App!',
      executables=executables,
      options=options)

Единственное отличие от предыдущих примеров заключается в том, что в список исключаемых модулей (параметр excludes в build_exe) включены только некоторые модули, которые нам явно не нужны, а модуль wx добавлен в список zip_include_packages, чтобы файлы *pyc из библиотеки wxPython попали в архив python36.zip.

Запускаем сборку с помощью команды

python setup.py build

Теперь содержимое папки build_windows выглядит следующим образом:

Появились новые файлы *.dll и *.pyd, относящиеся в wxPython. С ними ничего не поделаешь, их трогать нельзя. Обратите внимание, что появились файлы MSVCP140.dll и VCRUNTIME140.dll, относящиеся к Microsoft Visual C++ Redistributable. Если мы посмотрим на лог работы cx_Freeze, то увидим, что эти файлы были скопированы не из папки, где находится запускаемый файл интерпретатора Python, а из папки библиотеки wxPython:

Copying data from package pydoc_data...
copying C:\Program Files (x86)\Python36-32\DLLs\_hashlib.pyd -> build_windows\_hashlib.pyd
copying C:\Program Files (x86)\Python36-32\DLLs\_lzma.pyd -> build_windows\_lzma.pyd
copying C:\Program Files (x86)\Python36-32\DLLs\_socket.pyd -> build_windows\_socket.pyd
copying C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\wx\_core.cp36-win32.pyd -> build_windows\wx._core.pyd
copying C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\wx\wxbase30u_vc140.dll -> build_windows\wxbase30u_vc140.dll
copying C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\wx\MSVCP140.dll -> build_windows\MSVCP140.dll
copying C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\wx\VCRUNTIME140.dll -> build_windows\VCRUNTIME140.dll
copying C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\wx\wxbase30u_net_vc140.dll -> build_windows\wxbase30u_net_vc140.dll
copying C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\wx\wxmsw30u_core_vc140.dll -> build_windows\wxmsw30u_core_vc140.dll
copying C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\wx\siplib.cp36-win32.pyd -> build_windows\wx.siplib.pyd

Поэтому появление этих файлов не связано с параметром include_msvcr, который мы всегда устанавливаем в True. Если мы уберем этот параметр или присвоим ему значение False, файлы MSVCP140.dll и VCRUNTIME140.dll не пропадут из сборки. Это еще раз доказывает, что появлению этих файлов мы обязаны библиотеки wxPython, а не cx_Freeze.

Если заглянуть в архив python36.zip, то внутри мы увидим следующие файлы:

Здесь может быть достаточно трудно понять, какие модули нужны, а какие лишние, поэтому лучше все оставить как есть.

Если мы теперь запустим созданный файл hello_wx.exe, то увидим одну особенность - появилось графическое окно, но кроме него на заднем фоне открылось также консольное окно.

В этом нет ничего удивительного, ведь для запуска скриптов используется интерпретатор Python python.exe, который является консольным приложением. Но понятно, что обычному пользователю программу в таком виде отдавать нельзя, консольное окно нужно убрать.

Для этого в cx_Freeze предусмотрен специальный параметр конструктора класса Executable, который называется base. До сих пор мы не использовали этот параметр, что равносильно использованию значения "Console", что означает, что мы создаем консольное приложение. Другие возможные значения параметра base это "Win32GUI" и "Win32Service".

Для того, чтобы избавиться от фонового консольного окна, достаточно в конструктор класса Executable передать параметр base="Win32GUI". Сделаем это ((Example 12-1)):

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py',
                          targetName='hello_wx.exe',
                          base='Win32GUI')]

excludes = ['logging', 'unittest', 'email', 'html', 'http', 'urllib', 'xml',
            'unicodedata', 'bz2', 'select']

zip_include_packages = ['collections', 'encodings', 'importlib', 'wx']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'zip_include_packages': zip_include_packages,
        'build_exe': 'build_windows',
    }
}

setup(name='hello_world',
      version='0.0.12',
      description='My Hello World App!',
      executables=executables,
      options=options)

Если теперь выполнить сборку и запустить приложение hello_wx.exe, то появится графическое окно без фонового консольного окна:

Как видите, никаких проблем с созданием графических приложений с использованием библиотеки wxPython нет.

11. Установка иконки приложения

Библиотека cx_Freeze позволяет устанавливать иконку для создаваемых запускаемых файлов. До сих пор мы не устанавливали иконку, поэтому в Windows использовалась картинка по умолчанию для запускаемых файлов:

Чтобы поменять иконку запускаемого файла, достаточно в конструктор класса Executable передать параметр icon, который должен содержать строку с именем файла иконки. К нашему следующему примеру (Example 13) прилагается высокохудожественная иконка, которая будет установлена для файла hello_wx.exe. Чтобы ее установить, добавим в конструктор класса Executable параметр icon, который принимает строку с именем иконки для приложения:

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py',
                          targetName='hello_wx.exe',
                          base='Win32GUI',
                          icon='example.ico')]

excludes = ['logging', 'unittest', 'email', 'html', 'http', 'urllib', 'xml',
            'unicodedata', 'bz2', 'select']

zip_include_packages = ['collections', 'encodings', 'importlib', 'wx']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'zip_include_packages': zip_include_packages,
        'build_exe': 'build_windows',
    }
}

setup(name='hello_world',
      version='0.0.13',
      description='My Hello World App!',
      executables=executables,
      options=options)

В результате сборки запускаемый файл в проводнике Windows будет выглядеть следующим образом:

12. Создание инсталятора MSI

Кроме создания запускаемых файлов cx_Freeze позволяет создавать то, что под Windows называется инсталятором, а под Linux - пакетами. Под Windows cx_Freeze умеет создавать инсталятор в формате MSI, под Linux - пакеты в формате RPM, а под Mac OS X - пакеты .app и .dmg. В этой статье мы рассмотрим только создание инсталятора под Windows.

Давайте сделаем инсталятор на основе предыдущего примера. Единственное, что нам нужно будет сделать - убрать из словаря build_exe параметр build_exe, изменяющий папку для сборки. Как это ни странно, но если он установлен, то создатель инсталятора все равно ищет созданные файлы в папке, подразумеваемой по умолчанию. Возможно, это ошибка в cx_Freeze.

Новый скрипт для создания инсталятора ничем не примечателен по сравнению со скриптом для создания запускаемого файла (Example 14):

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py',
                          targetName='hello_wx.exe',
                          base='Win32GUI',
                          icon='example.ico')]

excludes = ['logging', 'unittest', 'email', 'html', 'http', 'urllib', 'xml',
            'unicodedata', 'bz2', 'select']

zip_include_packages = ['collections', 'encodings', 'importlib', 'wx']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'zip_include_packages': zip_include_packages,
    }
}

setup(name='hello_world',
      version='0.0.14',
      description='My Hello World App!',
      executables=executables,
      options=options)

Для того, чтобы создать инсталятор под Windows, вместо команды build мы должны использовать команду bdist_msi:

python setup.py bdist_msi

Внимание! Для того, чтобы инсталятор успешно создался, путь до скрипта сборки setup.py не должен содержать русские или другие не латинские буквы, иначе вы получите исключение:

Traceback (most recent call last):

  File "setup.py", line 27, in <module>
    options=options)
  File "C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\cx_Freeze\dist.py", line 349, in setup
    distutils.core.setup(**attrs)
  File "C:\Program Files (x86)\Python36-32\lib\distutils\core.py", line 148, in setup
    dist.run_commands()
  File "C:\Program Files (x86)\Python36-32\lib\distutils\dist.py", line 955, in run_commands
    self.run_command(cmd)
  File "C:\Program Files (x86)\Python36-32\lib\distutils\dist.py", line 974, in run_command
    cmd_obj.run()
  File "C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\cx_Freeze\windist.py", line 392, in run
    self.add_files()
  File "C:\Users\jenyay\AppData\Roaming\Python\Python36\site-packages\cx_Freeze\windist.py", line 133, in add_files
    cab.commit(db)
  File "C:\Program Files (x86)\Python36-32\lib\msilib\__init__.py", line 217, in commit
    FCICreate(filename, self.files)

ValueError: FCI error 1

После удачной сборки появятся две папки: папка build, внутри которой будет уже знакомая нам папка exe.win32-3.6 (мы ведь удалили параметр build_exe), и папка dist, внутри которой будет лежать файл hello_world-0.0.15-win32.msi. МЫ сделали очень простой инсталятор, если его запустить, то единственное, что он спросит пользователя, это папка, куда устанавливать программу:

Сейчас инсталятор даже не создает иконку в меню "Пуск", давайте ее добавим (Example 14-1). В исправленном файле setup.py мы передадим два новых параметра в конструктор класса Executable - shortcutName и shortcutDir. Параметр shortcutName задает имя, которое будет отображаться в меню "Пуск", а shortcutDir должно быть строкой из этой таблицы, указывающей, в какую системную папку следует поместить ярлык. В нашем случае мы поместим ярлык в C:\ProgramData\Microsoft\Windows\Start Menu\Programs, что задается параметром shortcutDir='ProgramMenuFolder'.

# coding: utf-8

from cx_Freeze import setup, Executable

executables = [Executable('example.py',
                          targetName='hello_wx.exe',
                          base='Win32GUI',
                          icon='example.ico',
                          shortcutName='Hello wxPython Application',
                          shortcutDir='ProgramMenuFolder')]

excludes = ['logging', 'unittest', 'email', 'html', 'http', 'urllib', 'xml',
            'unicodedata', 'bz2', 'select']

zip_include_packages = ['collections', 'encodings', 'importlib', 'wx']

options = {
    'build_exe': {
        'include_msvcr': True,
        'excludes': excludes,
        'zip_include_packages': zip_include_packages,
    }
}

setup(name='hello_world',
      version='0.0.14',
      description='My Hello World App!',
      executables=executables,
      options=options)

Если теперь выполнить команду python setup.py bdist_msi, после чего запустить инсталятор и выполнить установку программы, то в меню "Пуск" появится иконка нашей программы:

Создание MSI - это большая тема, достойная отдельной статьи, поэтому здесь мы ограничимся совсем коротким примером. К сожалению, в документации по cx_Freeze эта тема очень плохо описана, если вы хотите гибко настраивать параметры создаваемого инсталятора, то можете начать с этих двух ссылок - раз и два, но готовьтесь к тому, что вам придется разобраться с тем, как устроен формат MSI по MSDN. Хотя в документации к cx_Freeze говорится, что для создания сложных инсталяторов лучше использовать сторонние приложения, например Inno Setup.

13. Скрипт cxfreeze-quickstart для создания файла setup.py

До сих пор мы предполагали, что файл setup.py мы создавали сами с чистого листа. В этом нет ничего сложного, но для облегчения создания этого файла к библиотеке cx_Freeze прилагается скрипт cxfreeze-quickstart. После запуска этого скрипта он предложит ответить на несколько вопросов и создаст заготовку с уже частично настроенным файлом setup.py.

Но прежде, чем демонстрировать работу этого скрипта, надо сказать пару слов о запуске его под Windows. Дело в том, что скрипт cxfreeze-quickstart поставляется в виде python-скрипта в формате, принятом в Linux - в виде файла с именем cxfreeze-quickstart без расширения. Под Windows этот файл расположен в папке Scripts внутри папки с Python. Например, если вы устанавливали cx_Freeze без использования virtualenv в папку пользователя (без использования прав администратора), то это будет папка C:\Users\USERNAME\AppData\Roaming\Python\Scripts\ .

Содержимое файла cxfreeze-quickstart очень короткое:

#!c:\python27\python.exe
from cx_Freeze.setupwriter import main

main()

Под Windows, если вы хотите использовать скрипт cxfreeze-quickstart проще всего скопировать его из указанной выше папки в папку с вашим проектом, переименовать его в cxfreeze-quickstart.py и запустить с помощью команды

python cxfreeze-quickstart.py

Давайте запустим его и введем некоторые параметры (Example 15):

$ python cxfreeze-quickstart.py

Project name: My project
Version [1.0]: 0.0.14
Description: Example for article.
Python file to make executable from: example_15.py
Executable file name [example_15]: hello.exe
(C)onsole application, (G)UI application, or (S)ervice [C]: G
Save setup script to [setup.py]:

'''Setup script written to setup.py; run it as:

    python setup.py build

Run this now [n]?''' n

Для наглядности строки, которые выводит скрипт cxfreeze-quickstart, выделены полужирным шрифтом.

После того, как мы ответим на все вопросы, будет создан файл setup.py со следующим содержимым:

from cx_Freeze import setup, Executable

# Dependencies are automatically detected, but it might need
# fine tuning.
buildOptions = dict(packages = [], excludes = [])

import sys
base = 'Win32GUI' if sys.platform=='win32' else None

executables = [
    Executable('example_15.py', base=base, targetName = 'hello.exe')
]

setup(name='My project',
      version = '0.0.15',
      description = 'Example for article.',
      options = dict(build_exe = buildOptions),
      executables = executables)

А дальше мы можем заполнять недостающие параметры самостоятельно.

14. Заключение

Мы рассмотрели основы использования библиотеки cx_Freeze для создания запускаемых файлов из python-скриптов. Мы научились создавать файл с параметрами сборки, который обычно называется setup.py , как с нуля, так и с помощью скрипта cxfreeze-quickstart. Разобрались с некоторыми параметрами, которые влияют на создание запускаемых файлов, а именно: как исключать лишние модули из сборки, как добавлять необходимые модули вручную, как менять папку для сборки. Мы рассмотрели особенности создания запускаемых файлов из скриптов, создающих графический интерфейс на примере wxPython, рассмотрели основы для создания инсталятора MSI под Windows.

Но библиотека cx_Freeze предлагает еще больше возможностей, которые остались за рамками статьи. Например, мы ничего не сказали о задании параметров сборки через командную строку (эти параметры могут заменять параметры, указанные в файле setup.py). Мы ничего не сказали о сборке запускаемых файлов и инсталяторов под другие операционные системы (Linux и Mac OS). Также за рамками статьи остались некоторые параметры, которые можно использовать в скрипте сборки setup.py. Обо всем этом вы можете прочитать в официальной документации библиотеки cx_Freeze.

И в завершение надо сказать, что cx_Freeze не позволяет делать. Во-первых, с помощью cx_Freeze вы можете делать сборки только под ту операционную систему, в которой вы работаете в данный момент, т.е. cx_Freeze - не инструмент для кросскомпиляции. И, во-вторых, в отличие от других подобных инструментов (например, pyInstaller), cx_Freeze не позволяет упаковать все необходимые файлы в один exe-шник.

На этом мы закончим статью, надеюсь, что она будет вам полезна. Еще раз напомню ссылку на github, где вы можете скачать все примеры, используемые в статье - https://github.com/Jenyay/cx_freeze_examples

Возможно, вас также заинтересуют другие статьи по программированию на Python.

Немного рекламы

Вы можете подписаться на новости сайта через RSS, Группу Вконтакте или Канал в Telegram.
5 stars

Рейтинг 5.0/5. Всего 7 голос(а, ов)



Sergey 01.07.2017 - 16:27

Хоть я и не увлекаюсь сборкой standalone-приложений, больше предпочитая web-dev, но за один только труд и раскладывание ("разжевывание") по полочкам - автору жирный плюс.
Надеюсь что если кто-то озадачится вопросом сбора бинарей - наткнется на твою статью. Молодца )

Andrew 02.07.2017 - 11:51

Отличная статья - большой спасибо, все супер!
Единственный минус - жаль что на гитхабе нет PDF-ки с самой статьей.

Leon74 04.07.2017 - 11:57

Отличная статья! Пока только мельком просмотрел, но уровень виден сразу. В закладки, однозначно. Спасибо!


Подписаться на комментарии
Автор:
Тема:
 Ваш комментарий
 
 
Введите код 401