如何用PyInstaller将带图片的Tkinter GUI打包为单文件EXE?
Hey there! I know how frustrating it is when your bundled EXE still relies on external image files—let's get this sorted. The issue likely stems from two key points: not properly telling PyInstaller to include your images in the bundle, and ensuring your code correctly accesses the images when the EXE runs (since --onefile extracts files to a temporary folder at runtime).
Here's the step-by-step solution that should work for you:
Step 1: Create a PyInstaller Spec File
First, generate a spec file so we can explicitly define which resources to bundle. Run this command in your terminal:
pyi-makespec --onefile --noconsole myscript.py
This will create a myscript.spec file in your directory.
Step 2: Edit the Spec File to Include Images
Open the myscript.spec file and modify the datas section to include your KasonLogo.png. The datas list takes tuples of (source_path, destination_folder_in_bundle). For your case, it should look like this:
a = Analysis( ['myscript.py'], pathex=[], binaries=[], # Add this line with your image path datas=[('C:/Documents/Python/ScreenPartNumberProgram/KasonLogo.png', '.')], ... )
This tells PyInstaller to bundle the image and place it in the root of the extracted temporary folder at runtime.
Step 3: Update Your Python Code with Correct Path Handling
Replace your existing image loading code with this version. We'll use a robust resource_path function, and fix the PhotoImage reference issue (Tkinter can garbage collect images if the variable isn't retained):
from tkinter import Tk, Label from PIL import Image, ImageTk import sys import os def resource_path(relative_path): """ Get absolute path to resource, works for dev and for PyInstaller """ try: # PyInstaller creates a temp folder and stores path in _MEIPASS base_path = sys._MEIPASS except Exception: base_path = os.path.abspath(".") return os.path.join(base_path, relative_path) # Your main window setup main = Tk() lblLogo = Label(main) lblLogo.grid(row=3, column=11, rowspan=4) try: # Use resource_path to get the bundled image logo_path = resource_path("KasonLogo.png") # Use PIL to open and resize the image (more reliable than Tkinter's zoom/subsample) pil_image = Image.open(logo_path) # Resize equivalent to your original zoom(25,20) then subsample(50,50) # Original math: new_size = (width*25/50, height*20/50) = (width*0.5, height*0.4) pil_image = pil_image.resize((int(pil_image.width * 0.5), int(pil_image.height * 0.4)), Image.Resampling.LANCZOS) KasonLogo = ImageTk.PhotoImage(pil_image) # Keep a reference to the image to prevent garbage collection lblLogo.image = KasonLogo lblLogo.config(image=KasonLogo) # Set window icon icon_image = Image.open(logo_path) icon = ImageTk.PhotoImage(icon_image) main.tk.call('wm', 'iconphoto', main._w, icon) # Also keep a reference to the icon main.icon = icon except Exception as e: print(f"warning: could not load photos - {str(e)}") lblLogo.config(text="[KasonLogo.png]") main.mainloop()
Step 4: Bundle the EXE Using the Spec File
Now run PyInstaller with the spec file instead of the script directly:
pyinstaller myscript.spec
Key Fixes in This Approach:
- Spec File Configuration: We explicitly tell PyInstaller to include the image, so it gets packed into the EXE.
- Resource Path Handling: The
resource_pathfunction correctly points to the temporary folder when running the EXE, and your dev directory when coding. - PIL Image Handling: Using PIL avoids some of Tkinter's PhotoImage limitations, and we explicitly retain references to the images to prevent them from being garbage collected (a common cause of missing images).
- Resize Calculation: We replaced
zoom/subsamplewith PIL'sresizefor more predictable behavior, but kept the same scaling ratio you had.
This should result in an EXE that runs without needing external image files—everything is bundled inside!
内容的提问来源于stack exchange,提问作者jaredmt

