Skip to content

How to Convert Python Script to EXE (Executable)

Learn how to package Python scripts into standalone Windows executables using PyInstaller, including GUI apps, custom icons, data files, and troubleshooting common issues.

To share Python programs with users who don't have Python installed, you need to bundle your script into a standalone executable. PyInstaller is the industry standard tool for this task.

This guide covers basic conversion, GUI applications, and troubleshooting common issues.

Installation

pip install pyinstaller

Basic Conversion

Create a single executable file:

pyinstaller --onefile myscript.py

The executable appears in the dist/ folder:

project/
├── myscript.py
├── build/          # Temporary build files
├── dist/
│   └── myscript.exe  # Your executable
└── myscript.spec   # Build configuration

GUI Applications (No Console)

For Tkinter, PyQt, or other GUI apps, hide the console window:

pyinstaller --onefile --noconsole app.py

Or use the shorthand:

pyinstaller -F -w app.py

Adding a Custom Icon

pyinstaller --onefile --icon=myicon.ico app.py

Common Flags Reference

Flag Short Purpose
--onefile -F Bundle everything into single EXE
--noconsole -w Hide console window (GUI apps)
--console -c Show console (default)
--icon=file.ico -i Set application icon
--name=AppName -n Custom executable name
--noconfirm -y Overwrite without asking
--clean Clean cache before building
--add-data Include additional files

Including Data Files

Bundle additional resources like images, configs, or databases:

pyinstaller --onefile --add-data "images;images" --add-data "config.json;." app.py

pyinstaller --onefile --add-data "images:images" --add-data "config.json:." app.py

Accessing Bundled Files in Code

import sys
import os

def resource_path(relative_path):
    """Get path to resource, works for dev and PyInstaller."""
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)
    return os.path.join(os.path.dirname(__file__), relative_path)

config_file = resource_path("config.json")
image_path = resource_path("images/logo.png")

Using a Spec File

For complex builds, customize the .spec file:

pyinstaller --onefile --noconsole --name MyApp app.py

pyinstaller myapp.spec

Example Spec File Modifications

a = Analysis(
    ['app.py'],
    pathex=[],
    binaries=[],
    datas=[
        ('images', 'images'),
        ('config.json', '.'),
    ],
    hiddenimports=['pandas', 'numpy'],  # Add missing imports
    ...
)

Hidden Imports

When PyInstaller misses dynamic imports:

pyinstaller --onefile --hidden-import=sklearn.neighbors app.py

Or in the spec file:

hiddenimports=['sklearn.neighbors', 'pandas._libs.tslibs.timedeltas']

Reducing File Size

Exclude Unused Modules

pyinstaller --onefile --exclude-module matplotlib --exclude-module tkinter app.py

Use Virtual Environment

Build from a clean virtual environment with only required packages:

python -m venv build_env
build_env\Scripts\activate
pip install pyinstaller
pip install -r requirements.txt  # Only what's needed
pyinstaller --onefile app.py

Use UPX Compression

pyinstaller --onefile --upx-dir=/path/to/upx app.py

Cross-Platform Building

Troubleshooting

Antivirus False Positives

Windows Defender often flags unsigned PyInstaller EXEs:

Solutions:

  1. Digitally sign your executable
  2. Submit for analysis at Microsoft Security Intelligence
  3. Whitelist the dist/ folder

Missing DLLs or Modules

pyinstaller --onefile --debug=all app.py

Application Won't Start

Add console to debug:

pyinstaller --onefile --console app.py

Module Not Found Errors

import sys
if getattr(sys, 'frozen', False):
    import os
    os.chdir(sys._MEIPASS)

Alternative Tools

Tool Pros Cons
PyInstaller Most popular, well-documented Large file size
cx_Freeze Cross-platform More configuration needed
Nuitka Compiles to C, faster execution Longer build time
py2exe Windows-focused Windows only

Complete Build Script

import subprocess
import shutil
import os

def build():
    for folder in ['build', 'dist']:
        if os.path.exists(folder):
            shutil.rmtree(folder)
    
    cmd = [
        'pyinstaller',
        '--onefile',
        '--noconsole',
        '--name=MyApp',
        '--icon=assets/icon.ico',
        '--add-data=assets;assets',
        '--clean',
        'main.py'
    ]
    
    subprocess.run(cmd, check=True)
    print("Build complete! Check dist/MyApp.exe")

if __name__ == '__main__':
    build()

Summary

Scenario Command
Basic EXE pyinstaller --onefile script.py
GUI app pyinstaller -F -w app.py
With icon pyinstaller -F -w -i icon.ico app.py
With data pyinstaller -F --add-data "data;data" app.py
Debug build pyinstaller -F -c --debug=all app.py
Navigation