Python打包成exe避坑指南
2026/9/23 7:45:10 网站建设 项目流程

Python 打包成 exe 避坑指南(PyInstaller 实战)

写 Python 的第十五年,我被问得最多的问题之一就是:"我写了个脚本,怎么发给不会装 Python 的同事用?"答案就是 PyInstaller——把 .py 打包成 .exe,双击就能跑。

但 PyInstaller 这个工具,说简单也简单,一行命令就能打包;说坑也真坑——打包出来的 exe 在你电脑上好好的,发给别人就报错;体积动不动就上百 MB;杀软还天天报警。这篇文章我把自己踩过的坑全趟了一遍,下面的截图全是我在本机真实跑出来的。


一、先装 PyInstaller,一行命令的事

装 PyInstaller 很简单,pip 一行:

pipinstallpyinstaller

装完后敲pyinstaller --version,看到版本号就说明装好了。我本机用的是 6.16.0,Python 3.12。


二、最小打包:一行命令搞定

我在 demo 目录里放了个最简单的待办清单 GUI 程序(todo.py),只有 960 字节。打包命令就一行:

pyinstaller--onefile--nameTodoApp todo.py

等个几十秒,终端刷一大堆 INFO 日志,最后看到Build complete!就成了。

打包完会生成三个东西:

  • build/:中间文件,可以删
  • dist/:成品目录,exe 就在这里
  • TodoApp.spec:配置文件,一般不用管

三、–onefile vs --onedir:单文件还是文件夹?

上面用的--onefile,打包出来就是一个单独的 exe 文件,11.1 MB。发给别人一个文件就完事了,看着很爽。

如果换成--onedir(或者不写,默认就是这个),打包出来是一个文件夹:exe 本身只有 1.7 MB,但旁边多了个_internal文件夹,所有依赖的 DLL、Python 解释器、库全在里面。整个文件夹加起来其实比 onefile 还大。

你看_internal里塞了 60 个文件:Python 解释器的 .pyd 扩展、tkinter 依赖的 tcl/tk 数据、一堆系统 DLL。这些就是你的程序跑起来需要的全部家当。onedir 模式就是把它们原封不动摊开,onefile 就是把这些全压缩进一个 exe 里。

怎么选?

模式优点缺点适用场景
--onefile单文件,方便分发启动慢(每次都要解压到临时目录)发给别人用的小程序
--onedir启动快,体积小(不重复压缩)要发一整个文件夹自己用的程序、内部工具

我的建议是:分发给别人就用 onefile,自己用就用 onedir。onefile 启动慢的问题,对一个双击就用的 GUI 程序来说,1-2 秒的等待完全可以接受。


四、–noconsole:GUI 程序别带黑框

这是最容易忘的一个参数。你写了个 tkinter GUI 程序,打包完双击运行——先蹦出来一个黑色的命令行窗口,然后才弹出你的 GUI。那个黑框就是控制台窗口。

加个--noconsole(或者简写-w)就没了:

pyinstaller--onefile--noconsole--nameTodoApp todo.py

注意:如果你是命令行程序(要 print 输出、要 input 输入),就别加这个参数,加了你就看不到任何输出了。这个参数只给 GUI 程序用。


五、这些坑我替你踩过了

5.1 坑一:hidden-import——你的库它找不到

最常见的报错就是ModuleNotFoundError: No module named 'xxx'。你明明装了这个库,Python 脚本里也 import 了,PyInstaller 打包的时候也没报错,结果一运行就崩。

我写了个用importlib.import_module("requests")动态导入的脚本,打包时没加--hidden-import,运行就报了上面这个错。原因是有些库在运行时才动态导入子模块,PyInstaller 静态分析的时候发现不了。

解决办法就是手动告诉它:

pyinstaller--onefile--hidden-import=requests --hidden-import=requests.packages.urllib3 todo.py

加上--hidden-import=requests重新打包,再运行就正常了——能正确输出 requests 版本号,还能成功请求 PyPI API。或者直接在脚本里静态 import 一下那个找不到的模块,PyInstaller 就能扫到了。

5.2 坑二:文件路径——别用相对路径

打包完你会发现,脚本里写的相对路径(比如open("data.txt"))全错了。因为 onefile 模式运行时,程序是解压到一个临时目录里跑的,你的当前工作目录根本不是 exe 所在的目录。

解决办法:用sys._MEIPASS找资源文件的真实路径:

importsys,osdefresource_path(relative_path):ifhasattr(sys,'_MEIPASS'):base=sys._MEIPASSelse:base=os.path.abspath(".")returnos.path.join(base,relative_path)

然后读文件就用resource_path("data.txt")。这个写法在开发时和打包后都能正常工作。

5.3 坑三:体积太大——一个 helloworld 凭什么 11 MB?

我那个只依赖 tkinter 的待办程序,打出来 11 MB。这还是因为我用的是 Anaconda 环境,里面带了一堆没用的库。如果你用的是 PyPI 官方源装的干净 Python,打包出来会小不少。

想再压小一点,有几个办法:

  • 用干净的虚拟环境打包(别在 Anaconda 大环境里打)
  • --exclude-module排除不用的模块
  • 用 UPX 压缩(网上有教程,但我个人觉得没必要,11 MB 现在根本不算事)

5.4 坑四:杀毒软件报毒——这个锅不是你的

PyInstaller 打包出来的 exe,经常被 Windows Defender、360、火绒报毒。这不是你的程序有毒,而是 PyInstaller 的打包方式和某些病毒的行为很像(自解压到临时目录、动态加载 DLL)。

解决办法:

  • 跟用户解释清楚,加白名单
  • 给 exe 做数字签名(这个要花钱买证书)
  • 别太慌,只要你代码干净,报毒就是误报

六、给你一个能直接抄的完整命令模板

折腾了这么多,我整理了一个最常用的完整命令,你直接改文件名就能用:

pyinstaller ^--onefile^--noconsole^--nameMyApp ^--iconapp.ico ^ --hidden-import=requests ^ --hidden-import=PIL.Image ^ --add-data"assets;assets"^ main.py

每个参数的意思:

  • --onefile:打单文件
  • --noconsole:GUI 程序去黑框
  • --name:exe 的名字
  • --icon:程序图标(.ico 文件)
  • --hidden-import:手动指定要包含的模块
  • --add-data:把资源文件(图片、配置)一起打包进去,分号前面是源路径,后面是打包后的路径

七、总结:打包就这几步

PyInstaller 不难,记住这几点就够了:

  1. 一行命令就能跑,先跑通再调参数
  2. –onefile 给别人,–onedir 给自己
  3. GUI 程序记得加 --noconsole
  4. 报 ModuleNotFoundError 就加 --hidden-import
  5. 读资源文件别用相对路径,用 sys._MEIPASS
  6. 体积大就换干净虚拟环境,别在 Anaconda 里打

打包不是什么高深技术,就是个体力活。把上面这些坑都避开了,你打包出来的 exe 基本就能在别人电脑上正常跑了。你打包的时候还遇到过什么奇葩报错?评论区聊聊,我看看能不能帮你解决。
-import**
5.读资源文件别用相对路径,用 sys._MEIPASS
6.体积大就换干净虚拟环境,别在 Anaconda 里打

打包不是什么高深技术,就是个体力活。把上面这些坑都避开了,你打包出来的 exe 基本就能在别人电脑上正常跑了。你打包的时候还遇到过什么奇葩报错?评论区聊聊,我看看能不能帮你解决。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询