Python技术迷

将 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 build
  • Nuitka: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 全部免费领