Zum Inhalt springen

PyInstaller

PyInstaller is a cross-platform tool that converts Python programs into standalone executables. It analyzes your Python code, bundles all dependencies (including the Python interpreter), and creates self-contained binaries for Windows, macOS, and Linux. This eliminates the need for end-users to have Python installed.

  • Cross-Platform: Build executables for Windows, macOS, and Linux
  • One-File Bundles: Create single .exe files with all dependencies included
  • Hidden Imports Detection: Automatically detects most Python imports
  • Console and GUI Support: Works with CLI apps and graphical interfaces
  • Code Obfuscation: Optional encryption of Python bytecode
  • Bootloader Customization: Modify startup behavior and splash screens
  • DLL/SO Support: Bundles compiled extensions and native libraries
  • No Source Exposure: Compiled to bytecode, not plain text
# Standard installation
pip install pyinstaller

# With optional dependencies for enhanced functionality
pip install pyinstaller[all]

# For development (from source)
git clone https://github.com/pyinstaller/pyinstaller
cd pyinstaller
pip install -e .
pyinstaller --version
# Create executable from script
pyinstaller script.py

# Generate single executable (one file)
pyinstaller --onefile script.py

# Build for specific OS (from current platform)
pyinstaller --onedir --windowed myapp.py
dist/
  ├── script/
  │   ├── script.exe (or script on Linux/macOS)
  │   ├── python39.dll
  │   ├── library.zip
  │   └── [dependencies]
  └── script.exe (--onefile mode)

build/
  └── script/
      └── [build artifacts]

script.spec
  [PyInstaller spec file - configuration]
OptionDescriptionExample
--onefileCreate single executable filepyinstaller --onefile app.py
--onedirCreate directory with executablepyinstaller --onedir app.py
--windowedNo console window (GUI apps)pyinstaller --windowed app.py
--consoleShow console window (default)pyinstaller --console app.py
-n NAMESet output namepyinstaller -n MyApp app.py
-i ICONAdd icon to executablepyinstaller -i app.ico app.py
--add-dataInclude data filespyinstaller --add-data 'src:src' app.py
--add-binaryInclude binary filespyinstaller --add-binary 'lib.so:.' app.py
-p PATHAdd import search pathpyinstaller -p ./libs app.py
--hidden-importForce include modulepyinstaller --hidden-import=module app.py
--collect-allCollect all submodulespyinstaller --collect-all numpy app.py
--stripStrip binaries (Linux/macOS)pyinstaller --strip app.py
--upx-dirUPX compression directorypyinstaller --upx-dir=/usr/bin app.py
--keyEncryption key for bytecodepyinstaller --key=mykey123 app.py
--splashShow splash screenpyinstaller --splash splash.png app.py

Create a spec file for advanced configuration:

# Generate spec file
pyi-makespec --onefile --windowed app.py

# Edit spec file for fine-tuning
nano app.spec

# Build using spec file
pyinstaller app.spec
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_submodules, collect_data_files

block_cipher = None

a = Analysis(
    ['app.py'],
    pathex=['.'],
    binaries=[],
    datas=[
        ('assets/', 'assets'),
        ('config.json', '.'),
    ],
    hiddenimports=['package.module'],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludedimports=['matplotlib', 'tensorflow'],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='MyApp',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=False,
    disable_windowed_traceback=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon='app.ico',
)
# Create Python script
cat > hello.py << 'EOF'
import argparse

def main():
    parser = argparse.ArgumentParser(description='Simple hello tool')
    parser.add_argument('name', help='Name to greet')
    args = parser.parse_args()
    print(f"Hello, {args.name}!")

if __name__ == '__main__':
    main()
EOF

# Build executable
pyinstaller --onefile --console hello.py

# Test
./dist/hello World
# Create GUI app
cat > gui_app.py << 'EOF'
import tkinter as tk
from tkinter import messagebox

class App:
    def __init__(self, root):
        self.root = root
        self.root.title("My Application")
        self.root.geometry("400x300")
        
        tk.Label(root, text="Hello World", font=("Arial", 20)).pack(pady=20)
        tk.Button(root, text="Click Me", command=self.on_click).pack()
    
    def on_click(self):
        messagebox.showinfo("Info", "Button clicked!")

if __name__ == '__main__':
    root = tk.Tk()
    app = App(root)
    root.mainloop()
EOF

# Build with icon
pyinstaller --onefile --windowed --icon=app.ico gui_app.py
# Project structure
# myproject/
# ├── main.py
# ├── assets/
# │   ├── logo.png
# │   └── config.ini
# └── data/
#     └── default.json

# Build with data files
pyinstaller --onefile \
  --windowed \
  --add-data 'assets:assets' \
  --add-data 'data:data' \
  --name MyApp \
  main.py
# Flask app with static files
cat > app.py << 'EOF'
from flask import Flask, render_template
import os

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=False, port=5000)
EOF

# Build with templates and static files
pyinstaller --onefile \
  --add-data 'templates:templates' \
  --add-data 'static:static' \
  --hidden-import=flask \
  app.py
# Build with native libraries
pyinstaller --onefile \
  --add-binary '/usr/lib/libcustom.so:.' \
  --hidden-import=numpy \
  --hidden-import=scipy \
  scientific_app.py
# Dynamic imports may not be detected
import importlib
module = importlib.import_module(user_input)

# Explicit hidden imports required
pyinstaller --hidden-import=requests \
  --hidden-import=pandas \
  --hidden-import=sklearn \
  app.py
PackageHidden ImportCommand
PIL/PillowPIL--hidden-import=PIL
NumPynumpy--hidden-import=numpy
Pandaspandas--hidden-import=pandas
SQLAlchemysqlalchemy--hidden-import=sqlalchemy
Djangodjango--hidden-import=django
Flaskflask--hidden-import=flask
Requestsrequests--hidden-import=requests
BeautifulSoupbs4--hidden-import=bs4
# Collect all submodules of a package
pyinstaller --collect-all=django \
  --collect-all=numpy \
  app.py

# Multiple packages
pyinstaller --collect-all=package1 \
  --collect-all=package2 \
  --collect-all=package3 \
  app.py
# Create Windows console application
pyinstaller --onefile --console app.py

# Create Windows GUI (no console)
pyinstaller --onefile --windowed app.py

# Add version information
pyinstaller --onefile \
  --version-file=version.txt \
  app.py

# Manifest file for Windows
pyinstaller --onefile \
  --manifest=manifest.xml \
  app.py

# Code signing (Windows)
pyinstaller --onefile \
  --distpath ./signed \
  app.py

# Then sign with signtool
signtool sign /f certificate.pfx /p password /t http://timestamp.server app.exe
# Create macOS app bundle
pyinstaller --onefile \
  --osx-bundle-identifier com.example.app \
  app.py

# Set macOS deployment target
MACOSX_DEPLOYMENT_TARGET=10.13 pyinstaller app.py

# Code signing for macOS
pyinstaller --onefile \
  --codesign-identity "Developer ID Application: Company" \
  app.py

# Notarization (required for distribution)
xcrun altool --notarize-app -f app.dmg -t osx -u email@example.com -p @keychain:altool-password
# Exclude unnecessary modules
pyinstaller --onefile \
  --exclude-module=matplotlib \
  --exclude-module=tensorflow \
  --exclude-module=pandas \
  app.py

# Use UPX compression
pyinstaller --onefile --upx-dir=/usr/bin app.py

# Strip binaries (Linux/macOS)
pyinstaller --onefile --strip app.py
ConfigurationSize
Default —onedir50-100 MB
—onefile60-120 MB
With UPX compression30-50 MB
Exclude dependencies20-40 MB
# Generate encryption key
python -c "from PyInstaller.utils.otp import generate_key; print(generate_key())"

# Build with encryption
pyinstaller --onefile \
  --key=<encryption-key> \
  app.py

# Note: Minimal security, not true obfuscation
# Explicitly add hidden imports
pyinstaller --onefile \
  --hidden-import=missing_module \
  app.py

# Check imports
python -c "import missing_module"

# Add search path
pyinstaller --onefile \
  -p /path/to/modules \
  app.py
# Include binary files
pyinstaller --onefile \
  --add-binary 'C:\path\to\lib.dll:.' \
  app.py

# On Linux
pyinstaller --onefile \
  --add-binary '/usr/lib/lib.so:.' \
  app.py
# Build with console output for debugging
pyinstaller --onefile --console app.py

# Run and check error messages
./dist/app.exe

# Verbose logging
pyinstaller --onefile --debug=all app.py
# Profile execution
python -m cProfile -o stats.prof app.py

# Analyze
python -m pstats stats.prof

# Build without debug info
pyinstaller --onefile --strip app.py
# Create custom hook
cat > hook_mymodule.py << 'EOF'
from PyInstaller.utils.hooks import get_module_file_attribute

def get_module_bin_files(name):
    return [('/path/to/binary', '.')]

def get_module_data_files(name):
    return [('/path/to/data', 'data')]

binaries = get_module_bin_files('mymodule')
datas = get_module_data_files('mymodule')
EOF

# Use custom hook
pyinstaller --hookspath=. --onefile app.py
# runtime_hook.py - runs before app starts
import os
import sys

# Add environment variables
os.environ['MY_VAR'] = 'value'

# Modify system paths
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib'))
# Create splash screen
pyinstaller --onefile \
  --splash splash.png \
  app.py

# Update splash during execution
from pyi_splash import update_text
update_text("Loading modules...")
#!/bin/bash

APP_NAME="MyApp"
VERSION="1.0.0"

# Windows
pyinstaller --onefile --windowed \
  -n "$APP_NAME" \
  --distpath "./dist/windows" \
  app.py

# macOS
pyinstaller --onefile --windowed \
  -n "$APP_NAME" \
  --distpath "./dist/macos" \
  app.py

# Linux
pyinstaller --onefile --console \
  -n "$APP_NAME" \
  --distpath "./dist/linux" \
  app.py

echo "Build complete!"
  • Bytecode Not Encrypted: Executables can be reverse-engineered
  • Use Additional Obfuscation: For sensitive code, use PyArmor or similar tools
  • Sign Your Executables: Use code signing to ensure authenticity
  • Verify Dependencies: Check all included libraries for vulnerabilities
  • No Root Access Required: Don’t request unnecessary privileges
  • Network Security: Validate all network operations and inputs
# Windows MSI installer (requires WiX Toolset)
heat dir dist -o files.wxs
candle files.wxs -o obj/
light obj/files.wixobj -out MyApp.msi

# macOS DMG (requires create-dmg)
create-dmg --volname "MyApp" \
  --icon MyApp.app 100 100 \
  MyApp.dmg dist/MyApp.app
# GitHub Actions example
name: Build Executables
on: [push]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [windows-latest, macos-latest, ubuntu-latest]
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-python@v2
        with:
          python-version: '3.11'
      - run: pip install pyinstaller -r requirements.txt
      - run: pyinstaller --onefile app.py
      - uses: actions/upload-artifact@v2
        with:
          name: executable-${{ matrix.os }}
          path: dist/
ToolSingle FileSizeSpeedLearning Curve
PyInstallerYesMediumFast startupLow
cx_FreezeYesLargeSlowerMedium
py2exeWindows onlyLargeFastLow
py2appmacOS onlyLargeFastLow
NuitkaYesSmallFastMedium
CythonYesSmallVery fastHigh
TaskCommand
Simple CLI apppyinstaller --onefile --console app.py
GUI applicationpyinstaller --onefile --windowed --icon=icon.ico app.py
Web app with assetspyinstaller --onefile --add-data 'templates:.' app.py
With data filespyinstaller --onefile --add-data 'data:data' app.py
Multiple dependenciespyinstaller --onefile --collect-all package app.py
Optimized buildpyinstaller --onefile --strip --upx-dir=/usr/bin app.py