Building a Python EXE for Windows

Building a Python EXE for Windows

Python apps are most commonly run in the Cpython interpreter or a Jupyter/Ipython notebooks, but that can make it hard to distribute an app to people who aren't familiar with the Python ecosystem. In this post, we'll look at how to create a stand-alone executable with the PyInstaller build tool.

Sample code

I've created a sample at github.com/dmahugh/python-exe-sample that demonstrates the basic concepts. You can clone that repo, follow the installation instructions in the README, and then you'll be able to create a stand-alone executable. This blog post provides additional information about some of issues and options in creating an executable from a Python app.

The sample is for Windows only, although there are only minor syntactical difference to create an executable for Linux or Mac OS.

The sample project

The sample project is just a very simple CLI tool to demonstrate the basics of building a self-contained executable from a Python app. It takes an OPC package as input (i.e., a file based on the Open Packaging Conventions), and display the content types, package relationships, or part names within the package.

OPC is based on common standards such as ZIP compression and XML, so only the Python standard library is needed for the sample app's functionality.

EXE file size

The opcinfo.exe file generated by the sample is 5.43MB total, and most of that (3.6MB) is the copy of CPython 3.6.8 that's embedded in it.

If you need to include more dependencies, or larger ones such as a GUI toolkit, you may find PyInstaller's onedir option useful, to keep the EXE file small and quick-launching.

PyInstaller options

The build-windows-exe.bat file in the sample project uses the PyInstaller CLI to create an EXE file, with these options:

  • Set the name of the generated executable to opcinfo.exe
  • Use the onefile option, which generates a single self-contained EXE file with no external dependencies.
  • Exclude from the executable the pytest library, which is only needed for dev/test.
  • Add two file as embedded resources in the EXE file: the version.txt file required by importlib, and a config.json file.
pyinstaller cli.py --name opcinfo --onefile --exclude-module=pytest --add-data
    venv\Lib\site-packages\importlib_resources\version.txt;importlib_resources
    --add-data opcreader\config.json;opcreader
For Linux-based operating systems, change the backslashes in the above command to forward slashes and the semicolons to colons.

There are many other PyInstaller options available, as covered in the documentation. My sample project is a command-line tool, but if you're creating an executable for a GUI app you'll probably want to the the -w or --noconsole option to not show a console window, and the -i option to specify an icon file.

Dev/test workflow

If you follow the installation instructions in the project repo, you'll have the opcreader package available for interactive testing and development. You can run cli.py to launch the tool, and any code changes are immediately shown. You don't need to re-build the executable except when you want to distribute an updated executable.

The tests use pytest's parametrize option to check for expected output from simple DOCX, PPTX, and XLSX files.