Python's zipapp: Build Executable Zip Applications – Real Python

생성일
Jan 16, 2022 09:34 PM
언어
Python
분야
URL
 
notion imagenotion image
Table of Contents
notion imagenotion image
A Python Zip application is a quick and cool option for you to bundle and distribute an executable application in a single ready-to-run file, which will make your end users’ experience more pleasant. If you want to learn about Python applications and how to create them using zipapp from the standard library, then this tutorial is for you.
You’ll be able to create Python Zip applications as a quick and accessible way to distribute your software products to your end users and clients.
In this tutorial, you’ll learn:
  • What a Python Zip application is
  • How Zip applications work internally
  • How to build Python Zip applications with zipapp
  • What standalone Python Zip apps are and how to create them
  • How to create Python Zip apps manually using command-line tools
You’ll also learn about a few third-party libraries for creating Zip applications that overcome some limitations of zipapp.
To better understand this tutorial, you need to know how to structure Python application layouts, run Python scripts, build Python packages, work with Python virtual environments, and install and manage dependencies with pip. You also need to be comfortable using the command line or terminal.
Free Bonus: Click here to get a Python Cheat Sheet and learn the basics of Python 3, like working with data types, dictionaries, lists, and Python functions.

Getting Started With Python Zip Applications

One of the most challenging problems in the Python ecosystem is finding an effective way to distribute executable applications, such as graphical user interface (GUI) and command-line interface (CLI) programs.
Compiled programming languages, such as C, C++, and Go, can generate executable files that you can run directly on different operating systems and architectures. This ability makes it easy for you to distribute software to your end users.
However, Python doesn’t work like that. Python is an interpreted language, which means that you need a suitable Python interpreter to run your applications. There’s no direct way to generate a standalone executable file that doesn’t need an interpreter to run.
There are many solutions out there that aim to solve this issue. You’ll find tools such as PyInstaller, py2exe, py2app, Nuitka, and more. Those tools allow you to create self-contained executable applications that you can distribute to your end users. However, setting these tools up can be a complex and challenging process.
Sometimes you don’t need that extra complexity. You just need to build an executable app from a script or a small program so that you can distribute it to your end users quickly. If your application is small enough and uses pure Python code, then you can be well-served with a Python Zip application.
notion image

What Is a Python Zip Application?

PEP 441 – Improving Python ZIP Application Support formalized the idea, terminology, and specification around Python Zip applications. This type of application consists of a single file that uses the ZIP file format and contains code that Python can execute as a program. These applications rely on Python’s ability to run code from ZIP files that have a __main__.py module at their root, which works as an entry-point script.
Python has been able to run scripts from ZIP files since versions 2.6 and 3.0. The steps to achieve that are pretty straightforward. You just need a ZIP file with a __main__.py module at its root. You can then pass that file to Python, which adds it to sys.path and executes __main__.py as a program. Having the application’s archive in sys.path allows you to access its code through Python’s import system.
As a quick example of how all that works, say you’re on a Unix-like operating system, such as Linux or macOS, and you run the following commands:
$ echo 'print("Hello, World!")' > __main__.py $ zip hello.zip __main__.py adding: __main__.py (stored 0%) $ python ./hello.zip Hello, World!
You use the echo command to create a __main__.py file containing the code print("Hello, World!"). Then you use the zip command to archive __main__.py into hello.zip. Once you’ve done that, you can run hello.zip as a program by passing the filename as an argument to the python command.
To round up the internal structure of Python Zip applications, you need a way to tell the operating system how to execute them. The ZIP file format allows you to prepend arbitrary data at the beginning of a ZIP archive. Python Zip applications take advantage of that feature to include a standard Unix shebang line in the application’s archive:
#!/usr/bin/env python3
On Unix systems, this line tells the operating system which program to use for executing the file at hand so that you can run the file directly without the python command. On Windows systems, the Python launcher properly understands the shebang line and runs the Zip application for you.
Even with a shebang line, you can always execute a Python Zip application by passing the application’s filename as an argument to the python command.
In summary, to build a Python Zip application, you need:
  • An archive that uses the standard ZIP file format and contains a __main__.py module at its root
  • An optional shebang line that specifies the appropriate Python interpreter to run the application
Besides the __main__.py module, your application’s ZIP file can contain Python modules and packages and any other arbitrary files. However, only .py, .pyc, and .pyo files are available for direct use through the import system. In other words, you can pack .pyd, .so, and .dll files in your application’s file but you won’t be able to use them unless you unzip them into your file system.
Note: The impossibility to execute code from .pyd, .so, and .dll files stored in a ZIP file is a limitation of the operating system. This limitation makes it difficult to create Zip applications that ship and use .pyd, .so, and .dll files.
The Python ecosystem is full of useful libraries and tools written in C or C++ to guarantee speed and efficiency. Even though you can bundle any of those libraries into a Zip application’s archive, you won’t be able to use them directly from there. You’ll need to unzip the library into your file system and then access its components from that new location.
PEP 441 proposed .pyz and .pyzw as file extensions for Python Zip applications. The .pyz extension identifies console or command-line applications, while the .pyzw extension refers to windowed or GUI applications.
On Unix systems, you can remove the .pyz extension if you prefer a plain command name for your CLI applications. On Windows, the .pyz and .pyzw files are executable files because the Python interpreter registers them as such.

Why Use Python Zip Applications?

Say you have a program that your team uses a lot for their internal workflow. The program has grown beyond a single-file script into a full-fledged application with multiple packages, modules, and files.
At this point, some team members struggle to install and set up every new version. They keep asking you for a faster and easier way to set up and run the program. In that situation, you should consider creating a Python Zip application to bundle your program into a single file and distribute it as a ready-to-run app to your coworkers.
Python Zip applications are an excellent option for publishing software that you must distribute as a single executable file. It’s also a handy way to distribute software using informal channels, such as sending it through a computer network or hosting it on an FTP server.
Python Zip applications are convenient and quick ways to package and distribute Python applications in a ready-to-run format that can make your end user’s life much more pleasant.
notion image

How Do I Build a Python Zip Application?

As you’ve already learned, a Python Zip application consists of a standard ZIP file containing a __main__.py module, which works as the application’s entry point. When you run the application, Python automatically adds its container (the ZIP file itself) to sys.path so that __main__.py can import objects from the modules and packages shaping the application.
To build a Python Zip application, you can run the following general steps:
  1. Create the application’s source directory containing a __main__.py module.
  1. Zip the application’s source directory.
  1. Add an optional Unix shebang line to define the interpreter for running the application.
  1. Make the application’s ZIP file executable. This step applies to Unix-like operating systems only.
These steps are pretty straightforward and quick to run. With them, you can build a Python Zip application manually in a couple of minutes if you have the required tools and knowledge. However, the Python standard library includes a more convenient and faster solution for you.
PEP 441 proposed the addition of a new module called zipapp to the standard library. This module facilitates the creation of Zip applications, and it’s been available since Python 3.5.
In this tutorial, you’ll focus on creating Python Zip applications using zipapp. However, you’ll also learn how to run the whole series of steps manually using different tools. This additional knowledge can help you understand the entire process of creating Python Zip applications more deeply. It’ll also be helpful if you’re using a Python version lower than 3.5.

Setting Up a Python Zip Application

Up to this point, you’ve learned what Python Zip applications are, how to structure them, why to use them, and what steps you need to follow when creating them. You’re ready to start building your own. First, though, you need to have a functional application or script to use for your Python Zip app.
For this tutorial, you’ll use a sample application called reader, which is a minimal web feed
To follow along, you should clone the repository of reader into your local machine. Open your command line in a working directory of your choice and run the following command:
$ git clone https://github.com/realpython/reader.git
This command downloads the full contents of the reader repository into a reader/ folder in your current directory.
Once you’ve cloned the repository, you need to install the application’s dependencies. First, you should create a Python virtual environment. Go ahead and run the following commands:
$ cd reader/ $ python3 -m venv ./venv $ source venv/bin/activate
These commands create and activate a new Python virtual environment in the reader/ directory, which is the root directory of the reader project.
Note: To create and activate a virtual environment on Windows, you can run the following commands:
C:\> python -m venv venv C:\> venv\Scripts\activate.bat
If you’re on a different platform, then you may need to check out Python’s official documentation on creating virtual environments.
Now you can install the dependencies of reader using pip:
(venv) $ python -m pip install feedparser html2text importlib_resources
Running the command above will install all the application’s dependencies in your active Python virtual environment.
Note: Since Python 3.7, importlib_resources is available in the standard library as importlib.resources. So, if you’re using a version greater than or equal to 3.7, you don’t need to install this library. Just change the corresponding import in the __init__.py file that defines the reader package.
Here’s an example of using reader to get a list of the latest articles, courses, podcast episodes, and other learning resources from Real Python:
(venv) $ python -m reader The latest tutorials from Real Python (https://realpython.com/) 0 The Django Template Language: Tags and Filters 1 Pass by Reference in Python: Best Practices 2 Using the "and" Boolean Operator in Python ...
This output will be different for you since reader lists the thirty latest learning resources in the feed. Each learning resource has an ID number. To get the contents of one item out of these learning resources, you can pass the corresponding ID number as a command-line argument to reader:
(venv) $ python -m reader 2 Using the "and" Boolean Operator in Python Python has three Boolean operators, or **logical operators** : `and`, `or`, and `not`. You can use them to check if certain conditions are met before deciding the execution path your programs will follow. In this tutorial, you'll learn about the `and` operator and how to use it in your code. ...
This command prints part of the contents of the article Using the “and” Boolean Operator in Python to your screen using the Markdown text format. You can read any of the available pieces of content by changing the ID number.
Note: The details of how reader works aren’t relevant for this tutorial. If you’re interested in the implementation, then check out How to Publish an Open-Source Python Package to PyPI. In particular, you can read over the section called A Quick Look at the Code.
To create a Zip application from the reader repository, you’ll mainly use the reader/ folder. This folder has the following structure:
reader/ | ├── config.cfg ├── feed.py ├── __init__.py ├── __main__.py └── viewer.py
The most significant fact to note from the reader/ directory is that it includes a __main__.py file. This file enables you to execute the package using the python -m reader command as you did before.
Having a __main__.py file provides the required entry-point script to create a Python Zip application. In this example, the __main__.py file is inside the reader package. If you create your Zip application using this directory structure, then your app won’t run because __main__.py won’t be able to import objects from reader.
To work around this, copy the reader package to an external directory called realpython/ and place the __main__.py file at its root. Then remove the __pycache__/ folder that results from running python -m reader, which you did before. You should end up with the following directory structure:
realpython/ │ ├── reader/ │ ├── __init__.py │ ├── config.cfg │ ├── feed.py │ └── viewer.py │ └── __main__.py
With this new directory structure, you’re ready to create your first Python Zip application with zipapp. That’s what you’ll do in the following section.
notion imagenotion image

Building a Python Zip Application With zipapp

To create your first Python Zip application, you’ll use zipapp. This module implements a user-friendly command-line interface that provides the required options for you to build a full-fledged Zip application with a single command. You can also use zipapp from your code through the module’s Python API, which mainly consists of a single function.
In the following two sections, you’ll learn about both approaches to building Zip applications using zipapp.

Using zipapp From the Command Line

The command-line interface of zipapp streamlines the process of packing Python applications into ZIP files. Internally, zipapp creates a Zip application from the source code by running the steps you learned before.
To run zipapp from the command line, you should use the following command syntax:
$ python -m zipapp <source> [OPTIONS]
If source is a directory, then this command creates a Zip application from that directory’s contents. If source is a file, then that file should be a ZIP file containing the application’s code. The contents of the input ZIP file are then copied to the target application archive.
Here’s a summary of the command-line options that zipapp accepts:
-o <output_filename> or --output=<output_filename>
Writes the Zip application into a file called output_filename. This option uses the output filename as you provide it. If you don’t provide this option, then zipapp uses the name of source with the .pyz extension.
-p <interpreter> or --python=<interpreter>
Adds a shebang line to the application’s archive. If you’re on a POSIX system, then zipapp makes the application’s archive executable. If you don’t provide this option, then your app’s archive won’t have a shebang and won’t be executable.
-m <main_function> or --main=<main_function>
Generates and writes a proper __main__.py file that executes main_function. The main_function argument should have the form "package.module:callable". You don’t need this option if you already have a __main__.py module.
-c or --compress
Compresses the contents of source using the Deflate compression method. By default, zipapp just stores the contents of source without compressing it, which could make your application run faster.
This table provides a minimal description of the command-line options for zipapp. For further details on specific behaviors of each option, check out the official documentation.
Now that you know the basics of using zipapp from the command line, it’s time to build the reader Zip application. Go back to your terminal window and run the following command:
(venv) $ python -m zipapp realpython/ \ -o realpython.pyz \ -p "/usr/bin/env python3"
In this command, you set the realpython/ directory as the source for your Zip application. With the -o option, you provide a name for the application’s archive, realpython.pyz. Finally, the -p option lets you set the interpreter that zipapp will use to build the shebang line.
That’s it! Now you’ll have a realpython.pyz file in your current directory. You’ll learn how to execute that file in a moment.
To showcase the -m and --main command-line options of zipapp, say you decide to change the reader project layout and rename __main__.py to cli.py while moving the file back to the reader package. Go ahead and create a copy of your realpython/ directory and make the suggested changes. After that, the copy of realpython/ should look like this:
realpython_copy/ │ └── reader/ ├── __init__.py ├── cli.py ├── config.cfg ├── feed.py └── viewer.py
At the moment, your application’s source directory doesn’t have a __main__.py module. The -m command-line option of zipapp allows you to generate it automatically:
$ python -m zipapp realpython_copy/ \ -o realpython.pyz \ -p "/usr/bin/env python3" \ -m "reader.cli:main"
This command takes the -m option with "reader.cli:main" as an argument. This input value tells zipapp that the entry-point callable for the Zip application is main() from the cli.py module in the reader package.
The resulting __main__.py file has the following contents:
# -*- coding: utf-8 -*- import reader.cli reader.cli.main()
This __main__.py file is then packaged, along with your application’s source, into a ZIP archive named realpython.pyz.
notion image

Using zipapp From Python Code

Python’s zipapp also has an application programming interface (API) that you can use from your Python code. This API mostly consists of a function called create_archive(). With this function, you can create a Python Zip application quickly:
>>> import zipapp >>> zipapp.create_archive( ... source="realpython/", ... target="realpython.pyz", ... interpreter="/usr/bin/env python3", ... )
This call to create_archive() takes a first argument called source that represents the source of your Zip application. The second argument, target, holds the filename for the app’s archive. Finally, interpreter holds the interpreter to build and add as the shebang line to the app’s ZIP archive.
Here’s a summary of the arguments that create_archive() can take:
  • source can take the following objects:
    • A string-based path to an existing source directory
    • A path-like object referring to an existing source directory
    • A string-based path to an existing Zip application archive
    • A path-like object referring to an existing Zip application archive
    • A file-like object opened for reading and pointing to an existing Zip application archive
  • target accepts the following objects:
    • A string-based path to the target Zip application file
    • A path-like object to the target Zip application file
  • interpreter specifies a Python interpreter, which is written as a shebang line at the beginning of the resulting application archive. Omitting this argument results in no shebang line and no execution permission for the application.
  • main specifies the name of the callable that zipapp will use as the entry point for the target archive. You provide a value for main when you don’t have a __main__.py file.
  • filter takes a Boolean-valued function that should return True if a given file in the source directory should be added to the final Zip application file.
  • compressed accepts a Boolean value that determines whether you want to compress the source files or not.
Most of these arguments have an equivalent option in the command-line interface of zipapp. The example above uses just the first three arguments. Depending on your specific needs, you might use other arguments as well.

Running a Python Zip Application

So far, you’ve learned how to create Python Zip applications using zipapp from the command line and Python code. Now it’s time to run your realpython.pyz app to make sure it works.
If you’re on a Unix-like system, then you can run your application by executing the following:
(venv) $ ./realpython.pyz The latest tutorials from Real Python (https://realpython.com/) 0 The Django Template Language: Tags and Filters 1 Pass by Reference in Python: Best Practices 2 Using the "and" Boolean Operator in Python ...
Cool! It works! Now you have a single application file that you can quickly share with friends and colleagues.
You don’t need to invoke Python to run the application from the command line anymore. Since your Zip application archive has a shebang line at the beginning, the operating system will automatically use your active Python interpreter to run the contents of the target archive.
Note: For your application to run, you need to have all the required dependencies installed in your Python environment. Otherwise, you’ll get an ImportError.
If you’re on Windows, then your Python installation should have registered .pyz and .pyzw files and should be able to run them:
C:\> .\realpython.pyz The latest tutorials from Real Python (https://realpython.com/) 0 The Django Template Language: Tags and Filters 1 Pass by Reference in Python: Best Practices 2 Using the "and" Boolean Operator in Python ...
The reader application you’re using in this tutorial has a command-line interface, so it makes sense to run it from the command line or terminal window. However, if you have a GUI application, then you’ll be able to run it from your favorite file manager as you usually run executable apps.
Again, you can execute any Zip application by invoking the appropriate Python interpreter with the application’s filename as an argument:
$ python3 realpython.pyz The latest tutorials from Real Python (https://realpython.com/) 0 The Django Template Language: Tags and Filters 1 Pass by Reference in Python: Best Practices 2 Using the "and" Boolean Operator in Python ...
In this example, you use the system Python 3.x installation to run realpython.pyz. If you have many Python versions on your system, then you may need to be more specific and use a command like python3.9 realpython.pyz.
Note that whatever interpreter you use, you need to have the application’s dependencies installed. Otherwise, your application will fail. Having unsatisfied dependencies is a common issue with Python Zip applications. To work around this annoying situation, you can create a standalone application, which is the topic of the following section.
notion imagenotion image

Creating a Standalone Python Zip App With zipapp

You can also use zipapp to create self-contained or standalone Python Zip applications. This type of application bundles all its dependencies into the app’s ZIP file. This way, your end users only need a suitable Python interpreter for running the application. They don’t need to worry about dependencies.
To create a standalone Python Zip application, you first need to install its dependencies into the source directory using pip. Go ahead and create a copy of your realpython/ directory with the name realpython_sa/. Then run the following command to install the app’s dependencies:
(venv) $ python -m pip install feedparser html2text importlib_resources \ --target realpython_sa/
This command installs all the dependencies of reader using pip install with the --target option. The documentation of pip says that this option allows you to install packages into a target directory. In this example, that directory must be your app’s source directory, realpython_sa/.
Note: If your application has a requirements.txt file, then you can take a shortcut to install the dependencies.
You can run the following command instead:
(venv) $ python -m pip install \ -r requirements.txt \ --target app_directory/
With this command, you install all the dependencies listed in the app’s requirements.txt file into the app_directory/ folder.
Once you’ve installed the dependencies of reader into realpython_sa/, you can optionally remove the *.dist-info directories that pip creates. These directories contain several files with metadata that pip uses to manage the corresponding package. Since you don’t need that information any longer, you can get rid of it.
The final step in the process is to build the Zip application using zipapp as usual:
(venv) $ python -m zipapp realpython_sa/ \ -p "/usr/bin/env python3" \ -o realpython_sa.pyz \ -c
This command produces a standalone Python Zip application in realpython_sa.pyz. To run this application, your end users just need to have a proper Python 3 interpreter on their machine. The advantage of this kind of application compared to a regular Zip application is that your end users don’t need to install any dependencies to run the application. Go ahead and give it a try!
In the example above, you use the -c option of zipapp to compress the contents of realpython_sa/. This option can be fairly convenient for applications with many dependencies that take a significant amount of disk space.

Creating a Python Zip Application Manually

As you’ve already learned, zipapp has been available in the standard library since Python 3.5. If you’re using a Python version lower than that, then you can still build your Python Zip applications manually without the need for zipapp.
In the following two sections, you’ll learn how to create a Zip application using zipfile from the Python standard library. You’ll also learn how to use some command-line tools to achieve the same task.

Using Python’s zipfile

You already have the realpython/ directory with the reader application’s source files. The next step to manually build a Python Zip application from that directory is to archive it into a ZIP file. To do that, you can use zipfile. This module provides convenient tools to create, read, write, append, and list the contents of ZIP files.
The code below shows how to create the reader Zip application using zipfile.ZipFile and a few other tools. For example, the code relies on pathlib and stat to read the contents of the source directory and set execution permission to the resulting file:
# build_app.py import pathlib import stat import zipfile app_source = pathlib.Path("realpython/") app_filename = pathlib.Path("realpython.pyz") with open(app_filename, "wb") as app_file: # 1. Prepend a shebang line shebang_line = b"#!/usr/bin/env python3\n" app_file.write(shebang_line) # 2. Zip the app's source with zipfile.ZipFile(app_file, "w") as zip_app: for file in app_source.rglob("*"): member_file = file.relative_to(app_source) zip_app.write(file, member_file) # 3. Make the app executable (POSIX systems only) current_mode = app_filename.stat().st_mode exec_mode = stat.S_IEXEC app_filename.chmod(current_mode | exec_mode)
This code runs the three required steps to end up with a full-fledged Python Zip application. The first step adds a shebang line to the application’s file. It uses open() in a with statement to create a file object (app_file) to handle the application. Then it calls .write() to write the shebang line at the beginning of app_file.
Note: You should encode the shebang line in UTF-8 if you’re on Windows. If you’re on a POSIX system, such as Linux and macOS, you should encode it in whatever filesystem encoding sys.getfilesystemencoding() returns.
The second step zips the application’s source directory contents using ZipFile in a nested with statement. The for loop iterates over the files in realpython/ using pathlib.Path.rglob() and writes them to zip_app. Note that .rglob() searches for files and directories recursively through the target folder, app_source.
The filename, member_file, of each file in the final ZIP archive needs to be relative to the app’s source directory to ensure that the internal structure of the application’s ZIP file matches the structure of the source, realpython/. That’s why you use pathlib.Path.relative_to() in the example above.
Finally, the third step makes the application’s file executable using pathlib.Path.chmod(). To do this, you first get the current mode of the file using pathlib.Path.stat() and then combine this mode with stat.S_IEXEC using the bitwise OR operator (|). Note that this step only has an effect on POSIX systems.
After running these steps, your realpython.pyz application is ready to go. Go ahead and give it a try from your command line.
notion image

Using Unix Command-Line Tools

If you’re on a Unix-like system, such as Linux and macOS, then you can also run the three steps from the previous section using specific tools from your command line. For example, you can use the zip command to zip the contents of the application’s source directory:
$ cd realpython/ $ zip -r ../realpython.zip *
In this example, you first cd into the realpython/ directory. Then you zip the contents of realpython/ into realpython.zip using the zip command with the -r option. This option recursively traverses the target directory.
Note: Another option would be to use Python’s zipfile from the command line.
To do so, run the following command from outside the realpython/ directory:
$ python -m zipfile --create realpython.zip realpython/*
The --create command-line option of zipfile allows you to create a ZIP file from a source directory. The asterisk (*) appended to the realpython/ directory tells zipfile to put the contents of that directory at the root of the resulting ZIP file.
The next step is to add a shebang line to the ZIP file, realpython.zip, and save it as realpython.pyz. To that end, you can use the echo and cat commands in a pipe:
$ cd .. $ echo '#!/usr/bin/env python3' | cat - realpython.zip > realpython.pyz
The cd .. command gets you back out of realpython/. The echo command sends '#!/usr/bin/env python3' to the standard output. The pipe character (|) passes the contents of the standard output to the cat command. Then cat concatenates the standard output (-) with the contents of realpython.zip. Finally, the greater-than sign (>) redirects the cat output to the realpython.pyz file.
Finally, you may want to make the application’s file executable with the chmod command:
$ chmod +x realpython.pyz
Here, chmod adds execution permission (+x) to realpython.pyz. Now you’re ready to run your application again, which you can do from the command line as usual.

Using Third-Party Tools to Create Python Apps

In the Python ecosystem, you’ll find a few third-party libraries that work similarly to zipapp. They provide many more features and can be useful to explore. In this section, you’ll learn about two of these third-party libraries: pex and shiv.
The pex project provides a tool to create PEX files. PEX stands for Python executable and is a file format that stores self-contained executable Python virtual environments. The pex tool packs these environments into ZIP files with a shebang line and a __main__.py module, which allows you to execute the resulting PEX file directly. The pex tool is an expansion on the ideas outlined in PEP 441.
To create an executable application with pex, you first need to install it:
(venv) $ python -m pip install pex (venv) $ pex --help pex [-o OUTPUT.PEX] [options] [-- arg1 arg2 ...] pex builds a PEX (Python Executable) file based on the given specifications: sources, requirements, their dependencies and other options. Command-line options can be provided in one or more files by prefixing the filenames with an @ symbol. These files must contain one argument per line. ...
The pex tool provides a rich set of options that allows you to fine-tune your PEX files. The following command shows how to create a PEX file for the reader project:
(venv) $ pex realpython-reader -c realpython -o realpython.pex
This command creates realpython.pex in your current directory. This file is a Python executable for reader. Note that pex handles the installations of reader and all its dependencies from PyPI. The reader project is available on PyPI with the name realpython-reader, which is why you use that name as the first argument to pex.
The -c option allows you to define which console script the application will use. In this case, the console script is realpython as defined in the setup.py file of reader. The -o option specifies the output file. As usual, you can execute ./realpython.pex from your command line to run the application.
Since the contents of the .pex file are unzipped before execution, PEX applications solve the limitation of zipapp applications and allow you to execute code from .pyd, .so, and .dll files.
A final detail to note is that pex creates and packs a Python virtual environment in the resulting PEX file. This behavior makes your Zip application a lot bigger than a regular app created with zipapp.
The second tool you’ll learn about in this section is shiv. It’s a command-line utility for building self-contained Python Zip applications as described in PEP 441. The advantage of shiv compared to zipapp is that shiv automatically includes all of the app’s dependencies in the final archive and makes them available in Python’s module search path.
To use shiv, you need to install it from PyPI:
(venv) $ python -m pip install shiv (venv) $ shiv --help Usage: shiv [OPTIONS] [PIP_ARGS]... Shiv is a command line utility for building fully self-contained Python zipapps as outlined in PEP 441, but with all their dependencies included! ...
The --help option shows a complete usage message that you can check for a quick understanding of how shiv works.
To build a Python Zip application with shiv, you need an installable Python application with a setup.py or pyproject.toml file. Fortunately, the original reader project from GitHub fulfills this requirement. Go back to the directory containing the cloned reader/ folder and run the following command:
(venv) $ shiv -c realpython \ -o realpython.pyz reader/ \ -p "/usr/bin/env python3"
Like the pex tool, shiv has a -c option to define a console script for the application. The -o and -p options allow you to provide an output filename and a proper Python interpreter, respectively.
Note: The command above works as expected. However, the current version of shiv (0.5.2) makes pip display a deprecation message regarding how it builds packages. Since shiv accepts pip arguments directly, you can place --use-feature=in-tree-build at the end of the command so that shiv uses pip safely.
Unlike zipapp, shiv enables you to use the .pyd, .so, and .dll files you store in the application’s archive. To do so, shiv includes a special bootstrap function within the archive. This function unpacks the application’s dependencies into a .shiv/ directory in your home folder and adds them to Python’s sys.path.
This feature allows you to create standalone applications that include libraries that are partially written in C and C++ for speed and efficiency, such as NumPy.
notion image

Conclusion

Having a quick and efficient way to distribute your Python executable applications can make the difference in satisfying your end users’ needs. Python Zip applications provide an effective and accessible solution for you to bundle and distribute ready-to-run applications. You can use zipapp from the Python standard library to quickly create your own executable Zip applications and pass them along to your end users.
In this tutorial, you learned:
  • What a Python Zip application is
  • How Zip applications work internally
  • How to build your own Python Zip applications with zipapp
  • What standalone Zip apps are and how to create them using pip and zipapp
  • How to create Python Zip applications manually using command-line tools
With this knowledge, you’re ready to quickly create Python Zip applications as a convenient way to distribute your Python programs and scripts to your end users.