Python PyInstaller Packaging — Core Concepts
What PyInstaller does
PyInstaller reads your Python script, discovers every module and shared library it depends on, and bundles everything — including the Python interpreter itself — into a distributable package. The end user does not need Python or any libraries installed.
One-folder vs one-file mode
PyInstaller offers two output modes:
- One-folder (default): Creates a
dist/myapp/directory containing the executable plus supporting files. Faster startup because files are already on disk. - One-file (
--onefile): Packs everything into a single executable. At startup, it extracts files to a temporary directory, adding 2-10 seconds of delay. Easier to distribute but slower to launch.
# One-folder (default)
pyinstaller main.py
# One-file
pyinstaller --onefile main.py
How dependency analysis works
PyInstaller uses a hook system to discover dependencies:
- Static analysis — scans
importandfrom X import Ystatements in your code. - Hooks — pre-written scripts that handle tricky packages. For example, the
hook-numpy.pyhook knows NumPy needs hidden shared libraries. - Runtime hooks — scripts that run before your code starts, setting up paths or environment variables.
When analysis misses something (common with dynamic imports), you add it manually:
pyinstaller --hidden-import=sklearn.utils._cython_blas main.py
The spec file
The first run generates a .spec file — a Python script that configures the build:
# main.spec
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[('assets/', 'assets/')], # include data files
hiddenimports=['requests'],
hookspath=[],
)
pyz = PYZ(a.pure)
exe = EXE(pyz, a.scripts, [], name='MyApp', console=False, icon='icon.ico')
coll = COLLECT(exe, a.binaries, a.datas, name='MyApp')
After editing, build from the spec file directly:
pyinstaller main.spec
Including data files
PyInstaller only bundles Python modules by default. Images, config files, templates, and databases must be explicitly included:
pyinstaller --add-data "config.json:." --add-data "templates/:templates/" main.py
In your code, use the sys._MEIPASS attribute to find bundled files at runtime:
import sys
import os
def resource_path(relative):
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative)
return os.path.join(os.path.abspath('.'), relative)
config = resource_path('config.json')
This handles both development (files in the current directory) and frozen (files extracted to a temp directory) modes.
Console vs windowed
--console(default) — shows a terminal window. Good for CLI tools and debugging.--windowed/--noconsole— hides the terminal. Required for GUI apps (Tkinter, PyQt, etc.) to look professional.
Common pitfalls
| Problem | Solution |
|---|---|
| Missing module at runtime | Add --hidden-import=module.name |
| Missing data files | Use --add-data or datas in spec |
| Antivirus false positives | Sign the executable with a code-signing certificate |
| Large output size | Use --exclude-module to strip unused packages |
| Slow one-file startup | Use one-folder mode instead |
Cross-platform reality
PyInstaller builds for the current platform only. You cannot build a Windows .exe on Linux. The common solutions:
- Build on each target OS manually
- Use CI/CD (GitHub Actions) with runners for Windows, macOS, and Linux
- Use Docker for Linux builds from any host
Common misconception
“PyInstaller compiles Python to machine code.”
It does not. Your Python code is still interpreted by a bundled Python runtime. PyInstaller is a packager, not a compiler. Tools like Nuitka or Cython actually compile Python to C/machine code. PyInstaller’s advantage is simplicity — it works with almost any Python code without modification.
One thing to remember: PyInstaller bundles your script, its dependencies, and the Python interpreter into a standalone package — master the spec file, --hidden-import, and --add-data to handle the edge cases that trip up most first-time users.
See Also
- Python Appimage Distribution An AppImage is like a portable app on a USB stick — download one file, double-click it, and your Python program runs on any Linux computer without installing anything.
- Python Briefcase Native Apps Imagine a travel agent who repacks your suitcase for each country's customs — Briefcase converts your Python app into proper native packages for every platform.
- Python Flatpak Packaging Flatpak wraps your Python app in a safe bubble that works on every Linux system — like a snow globe that keeps your program perfect inside.
- Python Mypyc Compilation Your type hints are not just for documentation — mypyc turns them into speed boosts by compiling typed Python into fast C extensions.
- Python Nuitka Compilation What if your Python code could run as fast as a race car instead of a bicycle? Nuitka translates Python into C to make that happen.