Zipapps in a Nutshell

Zipapps are a way to distribute Python applications and all of their dependencies in a single binary file. This is comparable to statically linked golang apps or Java's ‘executable JARs’. Their main advantage is that distributing and installing them is quite simple.

Running Python code directly from ZIP archives is nothing new, PEP 273 made its debut in 2001, as part of Python 2.3 in the form of the zipimport module. PEP 441 builds on this and describes mechanisms to bundle full applications into a single ZIP file that can be made executable. It was approved in 2015 and a first implementation appeared in Python 3.5 via the zipapp module.

See the PEP for details on how making a ZIP into an executable file works, but basically on POSIX systems the Python interpreter is called in a ‘bang path’ that is followed by the ZIP archive. The interpreter recognizes the ‘script’ is a whole application archive and acts accordingly. On Windows, zipapps MUST carry the .pyz extension which is bound to the py wrapper command, which in turn looks at the bang path and calls a matching Python interpreter from the installed set.

To display the bang path of a zipapp, use this command:

python3 -m zipapp --info foo.pyz

If you want to change the requested Python version to one that is actually installed or that you prefer, change the bang path as part of the installation process:

python3 -m zipapp -p '/usr/bin/env python3.8' -o ~/bin/foo foo.pyz

This can also be done on an ad-hoc basis, by explicitly calling the desired interpreter:

python3.8 foo.pyz …  # POSIX
py -3.8 foo.pyz …    # Windows

Well-known tools to build new zipapps, outside of the Python core, are pex (Twitter) and shiv (LinkedIn). See their documentation for details on bundling your own applications.

Setting Up Windows 10 for Zipapps

On Windows, because there is no ‘+x’ flag, things are a bit more complicated than on POSIX. Zipapps MUST have a .pyz extension, for which the py launcher is registered as the default application. The net effect is that such files become executable and are handed over to the launcher if you add a few environment settings to your machine.

In the user-specific environment settings, add a new PATHEXT variable (or extend an existing one), with the value %PATHEXT%;.PYZ. Also edit the PATH one and add a new %LOCALAPPDATA%\bin entry. Save everything (click “OK”), open a new command window, and verify the changes with

 echo %PATHEXT% & echo %PATH%

Create the new bin directory by calling md %LOCALAPPDATA%\bin. Now you can place a zipapp file like foo.pyz in that directory, and it is immediately callable as foo.

To get such a test subject, you can build shiv with itself:

git clone https://github.com/linkedin/shiv.git
cd shiv
py -3 -m venv --prompt shiv venv
venv\Scripts\activate.bat
python -m pip install -e .
shiv -e shiv.cli:main -o %LOCALAPPDATA%\bin\shiv.pyz .
deactivate
shiv --version

Variations

If that makes more sense to you, you can change the system-wide variables instead of the user-specific ones, and choose paths that are global for all users (like C:\usr\bin or similar).

To make zipapps available network-wide, you can use %APPDATA% to store the zipapps, so you only have to maintain them once in case you regularly work on several machines in the same network. Just make sure the same version of Python is used everywhere then.