Most will agree that Python is easy to learn and write. When it comes to sharing that beautiful code, there’s one problem, though! Sharing your program with someone that doesn’t know Python is a challenge! Your fellow Pythonistas will be perfectly able to pip install your package or run a Python script you send them. But what about your boss? The non-programmers? This is where PyInstaller comes to play.
PyInstaller bundles your application into a single, runnable file you can share with anyone. No Python installation is required; just click and run! PyInstaller makes life easier for those that want to share their work. This article explains how PyInstaller works and what limitations there are. Finally, we’ll convert a Python program into a runnable file that can be shared with anyone.
Table of contents
How to install PyInstaller
pip install pyinstaller
If you don’t know these tools, please follow the links and read up about them. If you use Poetry or Pipenv, add it as a developer dependency.
PyInstaller officially supports Windows 8 and up, macOS, and Linux, with some limitations you’ll learn about further on.
How PyInstaller works
PyInstaller has two modes:
- Single directory mode
- one-file mode
The most elegant (in terms of distributing your program) is the one-file mode. It’s a single file, e.g., a .exe on Windows, that you can share with someone. This single file extracts itself into a temporary directory and runs the extracted code. The other mode (single directory) is a directory you can share with someone. It contains all the required files in a single, flat directory.
Let’s dive deeper and see how PyInstaller does its magic.
PyInstaller creates a so-called bootloader, which bootstraps your Python program. This process is separated from your actual Python program. Hence you can expect to see two processes being launched: first the bootloader and then your own Python code. When you
pip install PyInstaller, this bootloader is created once. The installation will only succeed if the bootloader got built.
Finding the needed module files
When packaging your Python project, PyInstaller must determine which modules to include. E.g., most software will have at least a couple of import statements, importing either built-in Python modules or externally downloaded modules. PyInstaller analyses your code and finds all the import statements. It does so recursively. For example, if you import a package like the built-in JSON library, it will load the JSON module’s code and analyze any imports found in there. This process repeats until it has all the required files to include.
Some modules do more peculiar things, like using importlib to import other modules. In these cases, you can manually supply pyinstaller the extra modules to include. PyInstaller includes ‘hooks’ for well-known packages that do these things, so chances are you won’t ever need to do this!
Specifying data files
If your software requires data files, you’ll need to tell PyInstaller explicitly. This is done by modifying the so-called spec file PyInstaller creates on the first run. This spec file tells PyInstaller exactly how to process your script. This is an advanced topic, and if you need to do this, I suggest you read the manual page on spec files.
Creating a binary using PyInstaller
Let’s start with an extremely simple use case: a single Python file called
hello.py with the following contents:
We can use the following command to package our script:
After running this command, this is what happens:
- PyInstaller analyzes your script for any import
hello.specfile is created
- A build folder is created
- A dist folder is created, containing a folder called
dist/hello folder contains all the files required to run our program, including an executable. On Linux and macOS, this executable is called
hello, on Windows it will be called
hello.exe. This executable file is the bootloader I mentioned before. Run it and see for yourself if it works!
If you check the size of the generated directory, you’ll notice that it’s pretty big for such a small script. In my case (on Linux) it’s 15 megabytes, but you need to consider that there’s a lot of overhead. E.g., this dist dir includes a copy of the Python core library, several required libraries, etcetera.
Creating a single file
We’ve used the default settings, creating a directory in dist containing your project. If you want to create a single file, you need to use the
--onefile option. I like the latter because it’s easy to remember:
pyinstaller --onefile hello.py
PyInstaller will ask if it may remove the previous output directory, which is OK. The output will this time be a single file: hello.exe on Windows or simply
hello on Linux and macOS. Run it to verify if everything worked.
Importing external libraries
PyInstaller uses the PATH to find modules and packages. So if you used Poetry or Pipenv or a plain old virtualenv to install something like
requests, PyInstaller will be able to package it with your software just fine. Just make sure you activated the virtual environment so that PyInstaller can find the library in your PATH.
E.g., the following program works flawlessly as long as PyInstaller can find requests:
import requests print("Hello world") r = requests.get("https://example.org") print(r.status_code) print(r.text) print("Bye")
PyInstaller supports most of the well-known Python libraries out there, including the more advanced ones like:
- NumPy (which includes a lot of compiled C/C++ code)
- PyQT (for GUI programming)
You can refer to the PyInstaller wiki on GitHub for a more complete list, although this list is not exhaustive. You are encouraged just to try, as most packages will work just fine.
Useful PyInstaller command line options
Here are some more options you might need:
|Option||What it does|
|Output a directory (the default)|
|Output a single executable file|
|Use this name for the app, instead of using the script’s basename|
|Don’t open a console for standard input and output on Windows and macOS. This option also creates a macOS .app bundle on macOS. Use –icon <file> to specify an icon for the macOS app. This option is ignored on Linux.|
|Show a splash image during loading. Only useful in combination with |
|Apply the given icon to the Windows executable file.|
Unfortunately, it’s not all shimmer and shine.
Pyinstaller is not a cross-compiler, which means it can’t create binaries for macOS or Linux from Windows and vice versa. You have to run pyinstaller on each platform that you want to support. E.g., if you want to release your software for both Windows and macOS, you’ll have to build the Windows file on Windows and the macOS file on a macOS machine.
If your software uses external tools and applications, you must add them manually. If these tools are platform specific, you won’t be able to distribute to other platforms unless you find alternative tools that you can include on these other platforms.
Although sharing your code with someone else, using the same OS will work flawlessly most of the time, your mileage may vary when you are on different versions of an OS. If you want to use PyInstaller to distribute your package to many people, you’ll have to put a lot more care into the packaging. The PyInstaller documentation contains more detailed information per OS.
PyInstaller tips and tricks
Here are a few tips and tricks you might need.
Determine if your code is running in a bundle
import sys if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): print('running in a PyInstaller bundle') else: print('running in a normal Python process')
Add a splash screen
You can add a splash screen to your bundle, but it is an experimental feature that does not work on macOS. The bootloader shows the splash screen, so the user gets some affirmation that the program is loading/unpacking. You can even display some custom, dynamic text on the splash screen.
To show a splash screen, create an image and specify it with the
--splash <image file> option.
Manually add modules that PyInstaller did not recognize
Sometimes PyInstaller does not find all the imports in your code. This can happen, for example, when importing modules from within a function. You can add the missing libraries manually with the
--hiddenimport MODULENAME parameter. E.g., to add a missing colorama import, you would use:
pyinstaller --hiddenimport colorama
To add multiple packages, simply repeat the same option multiple times.
Learn more about PyInstaller
I cover PyInstaller more extensively in my course, Python Fundamentals II. In this course, you’ll get real-world practice by packaging the project we build in Python Fundamentals I. While doing so, we’ll run into a couple of typical problems. I’ll teach you how to diagnose and fix such issues.
In addition, the PyInstaller documentation is excellent, so I highly recommend you skim through there to see if there’s more that interests you.