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.
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__.pymodule 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.
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:
- Create the application’s source directory containing a
__main__.pymodule. - Zip the application’s source directory.
- Add an optional Unix shebang line to define the interpreter for running the application.
- 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 reader that reads the latest articles and resources from the Real Python 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.
Note: Check out Introduction to Git and GitHub for Python Developers if you’re not familiar with Git and GitHub.
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.
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:
| Options | Description |
|---|---|
-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.
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:
sourcecan 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
targetaccepts the following objects:- A string-based path to the target Zip application file
- A path-like object to the target Zip application file
interpreterspecifies 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.mainspecifies the name of the callable thatzipappwill use as the entry point for the target archive. You provide a value formainwhen you don’t have a__main__.pyfile.filtertakes a Boolean-valued function that should returnTrueif a given file in the source directory should be added to the final Zip application file.compressedaccepts 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.
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/