PyInstaller 打包 GUI 程序:无 CMD、文件夹形式

用 PyInstaller 打包一个 Python GUI 程序,想要”启动不弹 CMD 黑框、也不打成单个大文件”,一条命令搞定:

pyinstaller -w -D main.py

参数解释

-w(或 --noconsole:不开控制台窗口。适合 Tkinter、PyQt、PySide、wxPython 之类的 GUI。

-D(或 --onedir:文件夹形式打包。产物结构:

dist/
 └── main/
     ├── main.exe
     ├── python3x.dll
     └── ...(依赖 dll、pyd、资源)

-F 单文件相比,-D 启动快、崩溃少、易调试,就是需要发一整个文件夹。

加图标

pyinstaller -w -D -i app.ico main.py

.ico 文件建议 256×256 或多尺寸 ico。

常见错误组合

GUI 忘了 -w

pyinstaller -D main.py         # 会弹黑色 CMD 窗口

GUI 只想单文件反而用了 -F -w

pyinstaller -F -w main.py

能跑,但每次启动都要把打包体解压到临时目录,启动慢;sys._MEIPASS 拿资源要处理路径。

用 spec 文件更可控

第一次 pyinstaller 会生成 main.spec,之后修改这个文件重新构建:

pyinstaller main.spec

spec 里控制”无 CMD”的字段:

exe = EXE(
    pyz,
    a.scripts,
    name='main',
    console=False,       # 无 CMD 窗口
    icon='app.ico',
    ...
)

-w -D 起步足够,需要精细控制 hidden imports、数据文件、加签名时再上 spec。

加静态资源

程序里读了 templates/assets/ 之类的目录,-D 只会打二进制,不带资源,运行时找不到。加 --add-data

Windows(分隔符 ;):

pyinstaller -w -D main.py ^
  --add-data "templates;templates" ^
  --add-data "assets;assets"

macOS / Linux(分隔符 :):

pyinstaller -w -D main.py \
  --add-data "templates:templates" \
  --add-data "assets:assets"

代码里读资源要用 PyInstaller 兼容路径:

import sys, os

def resource_path(rel):
    base = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base, rel)

with open(resource_path("templates/index.html")) as f:
    ...

找不到某个包

装了包但打包运行时抛 ModuleNotFoundError——PyInstaller 静态分析漏了动态导入。补 hidden-import:

pyinstaller -w -D main.py --hidden-import=some_dynamic_module

大量子模块可以直接 collect-all

pyinstaller -w -D main.py --collect-all fastapi --collect-all uvicorn

一句话总结

pyinstaller -w -D main.py 是 GUI 程序打包最省心的一条命令。资源要 --add-data、动态导入要 --hidden-import--collect-all