Python Program Distribution and Installation Methods
Table of Contents
đ tldr; There are many methods to distribute and install Python programs. Here are some of the ways I've tinkered with recently. Recommendation: in most cases, use pipx. đ§
â ď¸ Disclaimer: I'm not a Python expert, I've just been writing more Python lately and trying to figure out how best to distribute it. There are several fascinating and useful ways to distribute Python programs, and I've tried a few of them, some successfully, some not so much.
Introduction
I like Python. Iâm not sure whyâŚmaybe because it seems the closest programming language to English, or maybe because itâs so flexible that you can be messy with it. A lot of people dislike it because itâs not typed, and because you can be messy with itâone can write some pretty unstructured Python code. Also, it doesnât compile into a single binary. I think that is part of the reason why Go has gained a lot of traction, and now, in a similar vein, we have Rust too. Another problem is that you have to deal with Python versions on the machine where the application is installed. It might be nice to distribute the Python runtime with the application.
So, problems to solve with distributing Python programs, at least by default:
- You donât have a single binaryâŚ.instead you have an entrypoint into a bunch of Python code and dependencies
- You donât have a single Python version embeddedâŚyou have to deal with all kinds of different system versions
- You typically have a lot of dependencies, Python has a lot of great libraries to take advantage of
- Some dependencies need to be compiled, netifaces as an example, so sometimes you might need a massive toolchain to build them, which makes the user experience a bit more complex (though Python Wheels help with this)
This led me to try a few different ways to distribute Python programs that might help with these problems. So far there have been five or so ways Iâve tinkered with to distribute Python programs.
- AppImage - NOTE: Didnât get this working
- PyInstaller - This did work and created a nice binary
- Pip
- Installer script
- Pipx - Probably the best choice for most situations
Installation and Distribution Methods
AppImage
AppImage is a very interesting idea, but I couldnât get it working with my Python application.
From the AppImage website:
âAs a user, I want to download an application from the original author, and run it on my Linux desktop system just like I would do with a Windows or Mac application.â
âAs an application author, I want to provide packages for Linux desktop systems, without the need to get it âintoâ a distribution and without having to build for gazillions of different distributions.â
I tinkered with AppImage for a while, but ended up just using PyInstaller. AppImage can package anything, whereas something like PyInstaller is more specific to Python applications. I think this is what tripped me up.
Iâll come back to AppImage later as I think itâs a great idea, and I have applications I use which are distributed as AppImagesâjust download, mark as executable and run, all in one file.
PyInstaller
PyInstaller bundles a Python application and all its dependencies into a single package. The user can run the packaged app without installing a Python interpreter or any modules. PyInstaller supports Python 3.8 and newer, and correctly bundles many major Python packages such as numpy, matplotlib, PyQt, wxPython, and others. - PyInstaller
There are a few ways to get a binary out of a Python program. PyInstaller is one of them, and this is the method that seems to work best for me.
Here I build a nice single binary that I can distribute.
pyinstaller \
--clean \
--onefile \
--name coolprog \
--workpath pyinstaller-build \
--distpath dist \
--hidden-import yaml \
--hidden-import netifaces \
--hidden-import redis \
--hidden-import coolprog.cli \
src/coolprog_main.py
This created a nice relatively small binary.
du -hsc dist/coolprog
16M dist/coolprog
16M total
All you have to do is distribute the binary! Having a single binary that works, that contains the Python runtime is amazing and feels like magic.
One issue that I have is that apparently sometimes you have to give it some hints regarding the dependencies, e.g. --hidden-import
. Iâm not clear on how you would know what hints to give.
Benefits:
- Everything in one binary
- This includes the Python runtime as well!
- And all the application dependencies
Pip
Pip is the Python package manager. Itâs what you typically use to install Python packages. If youâve used Python, youâve probably used pip.
A few points:
- pip installs your application and dependencies from pypi.org
- Anyone (yes anyone) can sign up for a PyPI account and upload their own packages (which is amazing in itself!)
- There is also a test PyPI server, which is useful for testing
Once youâve signed up for a PyPI account, setup your application, you can simply upload it to PyPI with something like twine, and then once it is uploaded, you can install it with pip.
pip install coolprog
However, in modern Linux distributions, you will get this kind of message:
ubuntu@coolprog-test:~$ pip install coolprog
error: externally-managed-environment
Ă This environment is externally managed
â°â> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.
If you wish to install a non-Debian-packaged Python package,
create a virtual environment using python3 -m venv path/to/venv.
Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
sure you have python3-full installed.
If you wish to install a non-Debian packaged Python application,
it may be easiest to use pipx install xyz, which will manage a
virtual environment for you. Make sure you have pipx installed.
See /usr/share/doc/python3.12/README.venv for more information.
note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
With this in mind, you will likely want to use pipx instead of pip as you donât want to mess with the system Python, say for example by breaking it during the installation of your application. That would not make you unpopular and would provide a poor user experience.
Pipx
Pipx is a tool for installing and running Python applications in isolated environments. Itâs like pip but specifically for Python applications rather than libraries.
The key benefits of pipx are:
- It installs each application in its own isolated virtual environment, preventing dependency conflicts between different applications
- It makes Python applications available globally on your system while keeping their dependencies contained
- Itâs easy to install, upgrade, and uninstall applications without affecting other Python tools
Nice feature: Pipx provides a clean installation experience - you don't get a big stream of text output showing the tens of complex dependencies that are being installed.
Here is an example of installing a Python application with pipx:
pipx install coolprog
e.g. output:
ubuntu@coolprog-test:~$ pipx install coolprog
installed package coolprog 0.2.0, installed using Python 3.12.3
These apps are now globally available
- coolprog
â ď¸ Note: '/home/ubuntu/.local/bin' is not on your PATH environment variable. These apps will
not be globally accessible until your PATH is updated. Run `pipx ensurepath` to
automatically add it, or manually modify your PATH in your shell's config file (i.e.
~/.bashrc).
done! ⨠đ â¨
So, just add it to your path and youâre good to go.
ubuntu@coolprog-test:~$ pipx ensurepath
Success! Added /home/ubuntu/.local/bin to the PATH environment variable.
Consider adding shell completions for pipx. Run 'pipx completions' for instructions.
You will need to open a new terminal or re-login for the PATH changes to take effect.
Otherwise pipx is ready to go! ⨠đ â¨
Nice!
Installer Script
Working with installer scripts was my first approach before moving to PyInstaller or pipx. The classic curl | bash
installation method, while discouraged for security reasons, offers simplicity that some people appreciate. The challenge lies in creating an installer script that provides both convenience and a great user experience. You need to consider how the installation impacts the userâs systemâwhat gets installed where, and how it interacts with existing components, etc., etc.
In the script I eventually settled on installing the application in its own Python virtual environment, which ironically ended up being quite similar to pipxâs approach. This makes sense, as isolated environments help avoid dependency conflicts while maintaining a clean, manageable installation. Sure you have a bit more complexity in the number of virtual environments you have to manage, but if there is an abstraction managing it, e.g. pipx, then itâs doable.
In the end, an installer script might make sense, but if pipx will work, it may be best to simply use pipx.
Other Methods
I didnât try any of these, but some other methods Iâve seen:
- Cx_Freeze
- Flatpak
- Py2Exe
- bbfreeze
- py2app
- PyOxidizer???
- Nuitka???
- Docker
And probably many more, some of which are new, and some of which are getting long in the tooth.
Conclusion
Distributing Python applications isnât easy. You are installing something into someoneâs computer, or server, and you have to make sure it works, that it isnât going to break their system or their Python environment, and that it is as easy as possible to install. But doing all that is more difficult than it sounds. Though, having now discovered pipx, I think it is the best choice for most situations.
I find all the work outside of writing code for an application to be much, much more complex than writing the code itself, and Iâve written about it here.
đ¤ Related: Check out my post about the pain points in programming beyond just writing code. đ