将 Python 脚本转换为 .Exe 文件的 3 种方法
昨晚十一点多在公司楼下等外卖的时候,群里有人问“脚本咋打成 exe 给测试点两下就能跑”,我顺手把我常用的三招整理了下。都是 Python 方案,Windows 场景说得多一点,Mac 打包成 app 也类似但不是 .exe,就不展开了。
你装的是哪个 Python、用没用虚拟环境、依赖是不是都装全了,这仨决定你半夜加不加班。建议:
用 python -m venv .venv单独建个环境;在这个环境里装依赖: pip install -r requirements.txt;打包工具也在这环境里装,别全局乱糟糟的。
另外,路径别带中文空格,图标、数据文件这些待会都要一起处理。
方法一:PyInstaller(最顺手,覆盖面广)
说人话:它把你的代码、依赖、解释器打包在一起;可以“一体化单文件”也可以“目录模式”。
安装:
pip install pyinstaller
最小可用——命令行窗口版:
pyinstaller -F hello.py
跑完会在 dist/hello.exe 出来一个单文件(-F/--onefile)。如果是 GUI 程序(比如 tkinter/pyqt),不想弹黑窗:
pyinstaller -F -w hello.py # -w/--windowed
加图标、版本信息(Windows):
pyinstaller -F -w ^
--icon=assets/app.ico ^
--version-file=version.txt ^
hello.py
version.txt 可以写:
# UTF-8
VSVersionInfo(
ffi=FixedFileInfo(filevers=(1,0,0,0), prodvers=(1,0,0,0), flags=0, OS=0x4, fileType=0x1, subtype=0x0, date=(0,0)),
kids=[StringFileInfo([StringTable('040904B0',[StringStruct('CompanyName','demo'),
StringStruct('FileDescription','示例'),
StringStruct('FileVersion','1.0.0')])]),
VarFileInfo([VarStruct('Translation',[1033,1200])])]
)
带静态资源(模板、图片、模型文件):
pyinstaller -F hello.py --add-data "templates;templates" --add-data "assets;assets"
注意 Windows 用分号分隔“源;目标”,Linux/WSL 是冒号“源:目标”。
运行时取资源(单文件模式需要 sys._MEIPASS):
import sys, os
defresource_path(rel):
if hasattr(sys, "_MEIPASS"):
return os.path.join(sys._MEIPASS, rel)
return os.path.join(os.path.abspath("."), rel)
with open(resource_path("templates/index.html"), "r", encoding="utf-8") as f:
html = f.read()
常见坑两句带过:
杀软误报: --onefile压得狠更容易被误杀,换 onedir(去掉-F)or 给白名单;体积大:可以装 upx然后--upx-dir压缩,但别无脑用;导入失败:用 --hidden-import pkg.submod显式加;“找不到 VCRUNTIME140.dll”:装 MSVC 运行库或者用 onedir。
方法二:cx_Freeze(配置化,跨平台味儿更浓)
它的思路是“写个 setup 脚本”,像打 wheel 那样打 exe,默认是目录模式(我个人觉得更稳定)。
安装:
pip install cx_Freeze
setup.py(或 build.py)示例,支持 GUI、依赖、资源打包:
from cx_Freeze import setup, Executable
import sys, pathlib
base = "Win32GUI"if sys.platform == "win32"elseNone
include_files = [
("templates", "templates"),
("assets/logo.png", "assets/logo.png"),
]
build_exe_options = {
"packages": ["requests"], # 你的依赖里需要“强制包含”的包
"excludes": ["tests"],
"include_files": include_files,
"optimize": 1, # 0/1/2
}
setup(
name="HelloApp",
version="1.0.0",
description="小示例",
options={"build_exe": build_exe_options},
executables=[Executable("hello.py", base=base, icon="assets/app.ico")]
)
打包:
python setup.py build
# 或
python -m cx_Freeze build
生成的可执行在 build/exe.win-amd64-3.x/HelloApp.exe。 需要“安装包”风格可以再配 bdist_msi(MSI 安装器):
python setup.py bdist_msi
读取资源不需要 _MEIPASS 那套,按相对路径放就行(因为是 onedir)。稳。
方法三:Nuitka(编译更像 C,启动快些,代码难反编)
Nuitka 会把 Python 编译成 C/C++,再用 MSVC/MinGW 编译为可执行文件或扩展模块。优点:启动速度、反编译难度;缺点:首次编译时间长,环境要求稍多。
安装:
pip install nuitka
最小用法(单 exe):
python -m nuitka --onefile hello.py
GUI、图标、跟随依赖、加资源(用文件打包插件):
python -m nuitka ^
--onefile --windows-disable-console ^
--windows-icon-from-ico=assets/app.ico ^
--include-data-dir=templates=templates ^
--include-data-files=assets/logo.png=assets/logo.png ^
--follow-imports ^
hello.py
编译器选择(Windows 推荐 MSVC,有 VS/Build Tools 的就行):
python -m nuitka --onefile --msvc=latest hello.py
常见点:
首编译会下载缓存和 C 编译依赖,时间别被吓到; 第三方包里有 C 扩展也能很好地一起编; 体积控制: --lto=yes --clang之类的优化旗子可以试,但别一次性全开,慢。
一个完整的小例子(含 GUI、网络、资源)
就做个 tkinter 小窗,点按钮请求个接口并显示图片(假装我们有个本地图片当占位)。
hello.py:
import tkinter as tk
from tkinter import messagebox
import threading, requests, io
from PIL import Image, ImageTk
import os, sys
defresource_path(rel):
if hasattr(sys, "_MEIPASS"): # 仅 PyInstaller onefile 需要
return os.path.join(sys._MEIPASS, rel)
return os.path.join(os.path.abspath("."), rel)
deffetch_text():
try:
r = requests.get("https://httpbin.org/get", timeout=5)
r.raise_for_status()
return r.json()["url"]
except Exception as e:
returnf"ERR: {e}"
defon_click():
btn.config(state="disabled")
lbl_var.set("请求中…")
defwork():
text = fetch_text()
# 模拟加载资源
img_path = resource_path("assets/logo.png")
try:
im = Image.open(img_path)
im = im.resize((64, 64))
photo = ImageTk.PhotoImage(im)
# tkinter 跨线程回到主线程更新
root.after(0, lambda: update_ui(text, photo))
except Exception as e:
root.after(0, lambda: update_ui(f"{text}\n加载图片失败: {e}", None))
threading.Thread(target=work, daemon=True).start()
defupdate_ui(text, photo):
lbl_var.set(text)
if photo:
img_lbl.config(image=photo)
img_lbl.image = photo
btn.config(state="normal")
root = tk.Tk()
root.title("HelloApp")
lbl_var = tk.StringVar(value="点下面按钮测试网络 + 资源加载")
tk.Label(root, textvariable=lbl_var, wraplength=360, justify="left").pack(padx=12, pady=12)
img_lbl = tk.Label(root)
img_lbl.pack()
btn = tk.Button(root, text="来一下", command=on_click)
btn.pack(pady=8)
root.minsize(380, 260)
root.mainloop()
依赖:
pip install requests pillow
三种打包都能跑:
PyInstaller: pyinstaller -F -w hello.py --add-data "assets;assets"cx_Freeze:在 setup.py里include_files=[("assets","assets")]然后python setup.py buildNuitka: python -m nuitka --onefile --windows-disable-console --include-data-dir=assets=assets hello.py
什么时候选哪个?
想快点出活儿、资料多:PyInstaller。一行命令的快乐,踩坑也好谷歌。 要 MSI、要 onedir 稳定部署:cx_Freeze。配置文件可控,跨平台感觉更“发行版”。 想提升启动速度、弱化反编译、长期交付:Nuitka。首次编译慢点,但产物“像编译
最后丢几个“真会用到”的小贴士
日志:打包后定位问题很难,记得把日志写到 ~\AppData\Local\YourApp\logs\xxx.log之类的地方;控制台 vs GUI:命令行工具别加 -w,否则看不到堆栈;版本锁定:把 requirements.txt固定到小版本,同一环境复现你才敢发版;杀软:公司电脑被误杀就让 IT 拉白名单,或改 onedir; CI:Windows Runner 上打包(GitHub Actions / GitLab CI),产物可复现,不用每次拉你本机。
就这些,我去热下外卖先,凉了口感真不行…
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领