tkinter to .exe with PyInstaller: Windows, macOS and Linux
Turn your tkinter program into an .exe, a Mac app or a Linux program that people can open with a double-click, with no Python on their computer.
Write and test the app at tkinter online or in the editor, where the window opens in the Window tab. Save the file with Download .py under the window on the tkinter page, or in the editor by right-clicking the file's tab and choosing Download .py. The program is standard tkinter, so the same file runs with Python on your computer.
PyInstaller does not compile your code to machine code. It copies the Python interpreter, your program as compiled .pyc files, every module the program imports and, for tkinter, the Tcl/Tk files, then adds a small launcher. The result runs only on the kind of system that built it: build the .exe on Windows, the Mac app on a Mac and the Linux program on Linux.
The example app
The steps below package this tip calculator. Any tkinter program works the same way: copy this one into the tkinter page to try it first.
import tkinter as tk
from tkinter import ttk
def calculate():
try:
bill = float(bill_entry.get())
percent = float(tip_box.get())
except ValueError:
result.set("Type numbers, like 48.50 and 15")
return
tip = bill * percent / 100
result.set(f"Tip: {tip:.2f} Total: {bill + tip:.2f}")
root = tk.Tk()
root.title("Tip Calculator")
frame = ttk.Frame(root, padding=16)
frame.grid()
ttk.Label(frame, text="Bill").grid(row=0, column=0, sticky="w")
bill_entry = ttk.Entry(frame, width=12)
bill_entry.grid(row=0, column=1, pady=4)
ttk.Label(frame, text="Tip %").grid(row=1, column=0, sticky="w")
tip_box = ttk.Spinbox(frame, from_=0, to=30, width=10)
tip_box.set(15)
tip_box.grid(row=1, column=1, pady=4)
ttk.Button(frame, text="Calculate", command=calculate).grid(row=2, column=0, columnspan=2, pady=8)
result = tk.StringVar()
ttk.Label(frame, textvariable=result).grid(row=3, column=0, columnspan=2)
root.mainloop()Build an .exe on Windows
1. Check Python and tkinter. Put app.py in a folder of its own, open a terminal (Command Prompt or PowerShell) and go to that folder with cd. The last command opens a small test window when tkinter works; close it to go on.
cd path\to\tip-calculator
python --version
python -m tkinterIf python is not found, install Python from python.org and open a new terminal.
2. Run the program with Python. PyInstaller packages the program as it is, so fix any error here first.
python app.py3. Install PyInstaller.
python -m pip install pyinstaller4. Build the .exe.
pyinstaller --onefile --windowed app.pyIf the terminal does not know pyinstaller, run python -m PyInstaller --onefile --windowed app.py instead (with a capital P and I).
5. Try it. Your program is dist\app.exe. Double-click it: the tip calculator opens with no console window behind it. To share it, send that one file.
PyInstaller also leaves a build folder of working files and app.spec, which records your options. Neither is needed to run the program.
| Option | What it does |
|---|---|
--onefile | Packs everything into one .exe (without it you get a folder) |
--windowed | No console window behind the app; -w and --noconsole mean the same |
--name "Tip Calculator" | Names the program (the default is the script's name) |
--icon app.ico | Gives the .exe file an icon |
--add-data "logo.png:." | Puts a file your app opens into the bundle |
--hidden-import name | Adds a module PyInstaller did not find in your code |
One file or one folder
Without --onefile, PyInstaller makes a folder, dist\app, holding app.exe and an _internal folder with Python and everything else. The program runs from that folder, so you share the whole folder, zipped.
One file (--onefile) | One folder (the default) | |
|---|---|---|
| What you share | One .exe | The folder, zipped |
| Starting up | A little slower: it unpacks itself into a temporary _MEI folder every time | Faster: nothing to unpack |
| Finding build problems | Harder | Easier: PyInstaller's manual suggests getting a one-folder build working first |
The temporary _MEI folder is deleted when the program closes, but stays behind if it crashes or is killed. For a small tkinter app on Windows, one file is the easiest to hand around.
Name and icon
--name names the program and --icon gives the .exe an icon from an .ico file. With Pillow installed (python -m pip install pillow), PyInstaller also accepts a PNG and converts it.
pyinstaller --onefile --windowed --name "Tip Calculator" --icon app.ico app.pyThe program is now dist\Tip Calculator.exe.
Ship images and other files
A program that opens a file by a bare name, like tk.PhotoImage(file="logo.png"), looks for it in the working folder: the folder the program was started from. That works when you run python app.py in the project folder. In the .exe it fails twice over: logo.png is not in the bundle, and the bundle's files are not in the working folder anyway.
Put the file in the bundle with --add-data "SOURCE:DEST": the file on your computer, a colon, then the folder inside the bundle, where . is the top folder. Repeat the option for each file, or give a whole folder.
pyinstaller --onefile --windowed --add-data "logo.png:." --add-data "sounds:sounds" app.pyOpen the file by a full path built from the folder your script is in:
import os
import tkinter as tk
HERE = os.path.dirname(os.path.abspath(__file__))
root = tk.Tk()
logo = tk.PhotoImage(file=os.path.join(HERE, "logo.png"))
tk.Label(root, image=logo).pack()
root.iconphoto(True, logo)
root.mainloop()Run as a script, __file__ is the path of app.py. Inside a bundle, PyInstaller sets __file__ to a path inside the bundle folder, which is where --add-data put logo.png. PyInstaller's manual calls __file__ the preferred way to find bundled files.
A bundled program can also tell where it is: PyInstaller sets sys.frozen to True and sys._MEIPASS to the bundle folder, which is the temporary _MEI folder of a one-file program or the _internal folder of a one-folder one. Many answers online use a resource_path() helper built on sys._MEIPASS; you write one in the first exercise below.
Run as a script: None Run as an .exe: C:\Users\ana\AppData\Local\Temp\_MEI52162
Rebuild with the spec file
Running pyinstaller on app.py writes app.spec afresh every time (Tip Calculator.spec with --name). The spec is Python code holding your options; to keep changes you make in it, build from the spec instead:
pyinstaller app.specFiles to bundle go in the datas= line inside Analysis(...), as (source, folder in the bundle) pairs:
datas=[('logo.png', '.'), ('sounds', 'sounds')],A build from a spec ignores most command-line options, since the spec holds them. Two that still work: --noconfirm replaces the last build without asking, and --clean clears PyInstaller's cache and temporary files before building.
Build a Mac app
On a Mac the steps are the same, with python3 in place of python. The python.org installer includes Tk. With Homebrew's Python, also install its Tk package for your Python version, for example brew install python-tk@3.14.
cd path/to/tip-calculator
python3 -m tkinter
python3 -m pip install pyinstaller
pyinstaller --windowed --name "Tip Calculator" app.pyIf pip stops with externally-managed-environment, install PyInstaller in a virtual environment, as in the Linux steps below.
On macOS --windowed makes an app: dist/Tip Calculator.app, which opens with a double-click. Leave out --onefile: an app is a folder anyway, and PyInstaller warns that one-file Mac apps clash with macOS security and will be refused from PyInstaller 7. For the icon, --icon takes an .icns file, or a PNG when Pillow is installed.
Zip the app to share it. macOS warns before opening an app from the internet that Apple has not checked. The person opening it tries once, then clicks Open Anyway in System Settings > Privacy & Security. To remove the warning for everyone, sign the app with a Developer ID certificate from the Apple Developer Program and send it to Apple for notarization.
Build a Linux program
Install tkinter, the venv module and binutils (PyInstaller uses its objdump and objcopy). On Debian or Ubuntu:
sudo apt install python3-tk python3-venv binutilsOn Fedora the tkinter package is python3-tkinter. Recent Debian and Ubuntu refuse pip install outside a virtual environment, so build inside one:
cd path/to/tip-calculator
python3 -m venv .venv
source .venv/bin/activate
pip install pyinstaller
pyinstaller --onefile --name tip-calculator app.py
./dist/tip-calculatorThe program is dist/tip-calculator, with no file extension. --windowed does nothing on Linux. A program built on a new Linux release may not start on an older one, so build on the oldest release you want it to run on.
When the app will not start
Failed to execute script
When a windowed .exe crashes, Windows shows a box titled Unhandled exception in script that says Failed to execute script 'app' due to unhandled exception, with the traceback under it. To see the error the way Python prints it, build without --windowed and start the program from a terminal, so the window stays open after the crash:
pyinstaller --onefile app.py
.\dist\app.exeOn Linux run ./dist/app the same way. On a Mac, run the program inside the app: "dist/Tip Calculator.app/Contents/MacOS/Tip Calculator". The last line of the traceback names the problem:
| The error says | What to do |
|---|---|
couldn't open "logo.png" or FileNotFoundError | The file is missing from the bundle or opened by a bare name: add it with --add-data and open it by a full path (see above) |
ModuleNotFoundError | PyInstaller missed an import it could not see in your code, such as one made by name with importlib: add --hidden-import and the module name |
RuntimeError: lost sys.stdin | The app calls input(), which a windowed build cannot answer (see below) |
PyInstaller lists the modules it could not find in build\app\warn-app.txt. Most entries there are harmless; look for the name from your error.
Antivirus or SmartScreen warnings
Some antivirus programs flag PyInstaller programs, harmless ones included, because malware is built with PyInstaller too and the scanners judge by resemblance; windowed builds are flagged more often. PyInstaller's maintainers suggest three things: report the false positive to the antivirus maker (Microsoft Defender takes files on its submission page), sign the program with a code-signing certificate, which makes warnings less common without ending them, or build PyInstaller's launcher (the bootloader) yourself.
Windows SmartScreen also warns about new programs downloaded from the internet that few people have run yet. More info, then Run anyway, opens the program.
Other tools
auto-py-to-exe puts PyInstaller's options in a window: install it with python -m pip install auto-py-to-exe, then run auto-py-to-exe. Nuitka works differently: it translates your program to C and compiles that, so it needs a C compiler, and it can also make one-file programs.
Practice exercises
Code on this page runs in your browser, with no bundle and no script file, so the tests play both parts: they set sys._MEIPASS to pretend to be a bundle, and set __file__ to a pretend script path.
Write resource_path(relative) that returns the full path of a file your app ships with:
sys has a _MEIPASS attribute: join relative onto that folder.os.path.dirname(os.path.abspath(__file__)).# in a bundle unpacked to /tmp/_MEI52162
resource_path("logo.png") -> /tmp/_MEI52162/logo.png
# as the script /home/ana/tip-calculator/app.py
resource_path("logo.png") -> /home/ana/tip-calculator/logo.pngYour app opens resource_path("logo.png") and resource_path("sounds/ding.wav"). For each file to land where that code looks, it needs its own --add-data SOURCE:DEST, where DEST is the folder inside the bundle: logo.png:. and sounds/ding.wav:sounds.
Write add_data_args(paths) that takes the relative paths your code opens and returns the list of arguments to add to the pyinstaller command: "--add-data", then "SOURCE:DEST", for each path in order. DEST is the folder part of the path, or . when there is no folder.
add_data_args(["logo.png", "sounds/ding.wav"])
-> ['--add-data', 'logo.png:.', '--add-data', 'sounds/ding.wav:sounds']