简介:PyInstaller-3.2.1 是面向 Python 开发者的打包工具资源,用于将 .py 或 .pyc 程序连同依赖库、动态链接库及数据文件封装为独立可执行文件,解决目标用户未安装 Python 环境而无法运行程序的痛点,尤其适合 Windows、Linux、macOS 多平台软件分发场景。该版本支持单文件与多文件发布、隐式及显式导入识别、C 扩展库处理,并可通过命令行自定义图标、排除多余文件等。压缩包约 3.01MB,内含 790 个文件,其中以 549 个 py 源码文件为主,另有 41 个 txt 说明、30 个 toc 依赖清单、25 个 h 头文件、10 个 spec 打包脚本及若干 exe、dll、png、ico 等资源,便于对照学习其完整工程结构。已有 599 人学习下载。阅读源码与 spec 配置,可理解 PyInstaller 从依赖分析到生成引导程序的工作原理,掌握常见打包报错的排查思路,并据此为自身项目定制高效稳定的打包方案。
1. PyInstaller 3.2.1 到底在解决什么问题:老项目的 exe 交付
PyInstaller 3.2.1 是 PyInstaller 在 Python 3.5/3.6 时代的一个稳定版本,很多人对它的第一印象是“老”,但真正需要它的人是那些还在维护老 Python 项目的工程师:他们有写好的 .py 工具要分发给同事或客户,对方机器上没有 Python 环境,也不该为了跑一个小工具装一整套运行时。PyInstaller 3.2.1 在这个场景里的价值,就是用一条命令把脚本、依赖和解释器打包成一个 Windows 可执行文件,做到双击即用。它尤其是那些要求行为稳定、不想跟随新版本改动而重新验证的老项目的最优解——很多团队到今天还在用它,不是不知道新版存在,而是“能跑就不动”的交付逻辑。本文围绕它讲清楚原理、参数和踩坑点,帮你在老环境里把 pyinstaller 打包 exe 这件事一次做对。
2. 打包前先看原理:3.2.1 的依赖分析、bootloader 和 onefile 结构
2.1 一个 py 文件变成 exe,中间经过了哪些步骤
PyInstaller 做的事情比“把 .py 塞进一个壳”要多。它会先分析入口脚本的 import 语句,把用到的模块和二进制依赖全部收集到一个文件清单里;然后把这些文件连同 Python 解释器的字节码一起打包,并在最外层生成一个 bootloader(引导程序)。运行时,bootloader 负责创建临时目录、解压资源、加载解释器、最后执行你的 main 代码。
3.2.1 的整体流程可以拆成三步。第一步是 Analysis,它扫描入口脚本,生成一个 .spec 文件并记录所有依赖项;第二步是 PYZ,把纯 Python 模块的字节码归档进一个归档文件,这一步和 zipimport 的机制类似;第三步是 EXE 或 COLLECT,生成最终的 bootloader 和产物。需要明确一点:bootloader 是一个独立的 C 程序,它先于 Python 解释器运行,所以你的 exe 在完全没有安装 Python 的机器上也能启动。3.2.1 和当前新版本在结构上没有本质差别,差别主要体现在对新语法、新 Python 版本的支持,以及对 Windows 系统行为的适配。
2.2 为什么选 3.2.1 而不选更“新”的版本
很多人问为什么还要用这个版本。真实原因通常有三类。一是 Python 版本被锁死,项目跑在 Python 3.5/3.6 上,3.2.1 是这组环境里一个相对成熟的组合,升级 PyInstaller 意味着要重新验证打包产物在几百台机器上的行为。二是企业内网或离线环境,服务器上装好的依赖和打包工具不能随意升级,内网源里有什么就用什么。三是行为一致性问题,新版本在默认参数、DPI 处理、加密选项上有改动,对一个已经上线分发两年的工具,升一次版等于重新做一轮兼容性测试。
3.2.1 之后的路径也值得了解:3.3.x 主要修 bug 并补上对 Python 3.6 的适配;4.0 是一次大的架构升级,引入了新的 bootloader 仓库和更复杂的配置项。而 3.2.1 的 bootloader 是静态链接的 C 程序,行为简单直接,这也是它的产物容易被杀毒软件盯上的原因之一——入口特征太明显。所以选 3.2.1 不是“图新”,而是“图不变”。需要避免的误用是:有人因为环境是 Python 3.8,硬要 pip install pyinstaller==3.2.1。这不是不能装,但装完大概率在运行时报错,后面避坑章节会专门说。
2.3 什么时候不该用 3.2.1
如果你的代码用了 Python 3.7 的 dataclasses、3.8 的海象运算符,或者依赖新版 PyInstaller 的 hook 机制(比如一些带动态链接库的第三方库),就不该用 3.2.1。PyInstaller 对第三方库的支持主要是靠 hook 文件实现的,老版本 hook 覆盖不全,遇到 PyQt5、OpenCV、TensorFlow 这类重型依赖时,常常要手工补 hidden import。
判断标准也简单:先在打包机上跑 python --version,如果高于 3.6,就老老实实考虑新版;如果确实是 3.5/3.6,那么 3.2.1 很合适。另一个判断点是产物体积和加载速度。3.2.1 的 onefile 模式在每次启动时都要把整个包解压到系统临时目录,如果你的工具是给客户高频使用的,这个启动延迟会非常影响体验,这时候用 onedir 模式更实际。总之,选版本先看解释器,再看依赖库的 hook 覆盖情况,最后看分发形态。
3. 用 3.2.1 打出一个干净的 exe:环境准备、命令与参数设置
3.1 先搭一个干净的打包环境
virtualenv 或 venv 都行,核心是让打包环境的 site-packages 里只有项目真正依赖的包。用全局 Python 打包很容易把无关的包、甚至包管理器本身的东西带进产物,导致 exe 体积大且经常出现莫名其妙的冲突。建议把打包环境单独建在项目目录之外,避免 build 产物和虚拟环境混在一起。
# 创建并激活虚拟环境(Python 3.5/3.6 下同样适用) python -m venv build_env # Windows 激活 build_env\Scripts\activate # 安装项目依赖 pip install -r requirements.txt # 安装打包工具,锁定 3.2.1 pip install pyinstaller==3.2.1这里有个细节:venv 在 Python 3.3+ 都有,老项目也可以用 virtualenv 替代,用法几乎一样。pip 安装时如果网络受限,可以提前下载 wheel 包放到内网源,然后指定-i指向内网地址。锁定版本的目的,是保证 spec 文件格式、hook 行为都和你查到的文档一致,不会出现“网上教程是 4.x 语法,本地 3.2.1 不认”的情况。安装完成后用pyinstaller --version确认输出为 3.2.1,再进入下一步。
3.2 最小打包命令:先跑通 onedir,再决定要不要 onefile
第一次打包我一般不用 onefile,先用 onedir。原因是 onedir 更容易排查缺文件、缺 dll 的问题,而且每次运行不需要解压,启动快。命令很简单:
# GUI 程序,不显示控制台,生成目录形式的产物 pyinstaller -D -w app.py按顺序解释-D和-w:-D也就是--onedir,生成dist\app\目录,所有依赖都以文件形式平铺在这个目录里;-w表示 Windows 程序不打开控制台窗口,适合带 GUI 的脚本。如果脚本里有 print 或者需要看日志,第一次可以先去掉-w,改用-c让控制台保留,这样能直接看到 Python traceback,排错效率高很多。
第一次跑完,你会看到项目根目录下出现了build、dist和app.spec三个东西。build目录是中间产物,删了也没事,下次打包会自动重建;dist目录里的app.exe是最终产物;app.spec是 PyInstaller 生成的项目描述文件。后续改参数可以直接改 spec,也可以继续用命令行。注意:以-D方式生成的 exe 只是一个引导入口,真正代码在目录里的其他文件中,只拷 exe 出去是跑不起来的。
3.3 常用参数与 3.2.1 的注意点
下面这张表是我在 3.2.1 下常用的参数组合,都是这个版本稳定支持的:
| 参数 | 作用 | 使用注意 |
|---|---|---|
-F/--onefile | 打包成单文件 | 运行时要解压到临时目录,启动慢且容易被安全软件盯着 |
-D/--onedir | 打包成目录 | 交付时需要整个目录一起拷,但排查问题方便 |
-w/--windowed | 不显示控制台 | GUI 程序用;纯命令行工具不要加 |
-c/--console | 显示控制台 | 和-w二选一 |
--icon | 指定 exe 图标 | 用 .ico 文件,不会改变杀软判定结果 |
--name | 指定产物名 | 不要用中文,避免编码问题 |
--hidden-import | 手动加入隐式导入的模块 | 缺模块时逐个补 |
--exclude-module | 排除无用模块 | 能减体积,也减少干扰 |
注意 3.2.1 和 4.0+ 有一个细节差别:老版本的--hidden-import参数,重复写多次只会生效最后一次,多个模块要一次性写全,或者直接去 spec 文件里改成列表。命令行版本可以这样写:
# 3.2.1 下这种方式最稳,多个隐藏导入一次性写完 pyinstaller -D -w --hidden-import=win32timezone --hidden-import=packaging.version app.py--hidden-import能把静态分析发现不了的模块强制加进打包列表,这个参数在打包带动态导入的代码时几乎是必用。--exclude-module则相反,用来砍掉你确定用不到的模块,比如没用 numpy 就把它排除掉,能明显缩小体积。这两个参数一加一减,配合 spec 文件,基本能覆盖大多数定制需求。
3.4 用 spec 文件精确控制打包内容
命令行参数适合快速验证,正式交付前我会把控制转移到 spec 文件。spec 文件是 Python 语法,里面最关键的部分是Analysis的hiddenimports、datas和excludes三个字段。一个典型的 spec 片段如下:
a = Analysis(['app.py'], pathex=['.'], binaries=[], datas=[('assets/', 'assets')], hiddenimports=['pandas._libs.skiplist'], hookspath=[], runtime_hooks=[], excludes=['numpy', 'PIL'], noarchive=False) pyz = PYZ(a.pure) exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, name='app', console=False, icon='app.ico')datas里的元组是“源路径, 目标目录”的配对,第一个参数告诉 PyInstaller 从项目哪里找文件,第二个参数决定这些文件在运行时被解压到哪里。上面例子里('assets/', 'assets')会把 assets 目录原样打进包内,运行时代码用相对路径assets/xxx就能访问。hiddenimports用来补动态导入的模块,excludes排除不用的库以缩小体积。改完 spec 后直接运行pyinstaller app.spec就可以,不用再写一长串命令行。
这里有一个版本差异要特别提醒:3.2.1 在 onedir 模式下,spec 文件末尾通常还会有一段COLLECT步骤,负责把分散的文件收集到目标目录;而 onefile 模式则没有这一步。如果你从网上拷了一个新版 PyInstaller 生成的 spec 文件,里面可能会带module_collection_mode等 3.2.1 根本不认识的字段,运行会直接报错。所以 spec 文件最好由当前版本的 PyInstaller 自己生成,再手动修改,而不是从新版本项目里复制过来改两笔就完事。
4. 打包避坑指南:3.2.1 最常见的 5 个报错和误报问题
4.1 打包产物被 Windows Defender 或其他杀软报毒
现象:exe 刚生成就被隔离,或者交付给用户后,用户机器上的安全软件直接把文件删掉。
原因:PyInstaller 的 bootloader 特征比较明显,入口点和区段名被安全引擎标记为风险;onefile 模式运行时解压临时目录的行为也很容易被行为检测盯上;如果还开了 UPX 加壳,误报率会进一步上升。3.2.1 时代的 bootloader 是静态链接的,没有后续版本里那些规避特征处理,所以这事儿在它身上尤其常见。
解决:优先改用 onedir 模式交付,不做 UPX 压缩;给 exe 加数字签名或至少加上公司信息,能降低一部分启发式误报;在企业内部,把产物提交给安全团队加白名单。排查时把产物上传到多引擎查杀平台,看具体报毒名称来区分是启发式误报还是真被注入了东西。最后提醒一句:如果某次打包突然报毒而之前不报,先检查是不是打包环境被污染了,不要盲目改参数。
4.2 onefile 模式启动慢、临时目录残留
现象:双击 exe 后等了 3 到 5 秒才出现窗口;程序退出后%TEMP%下有_MEI开头的目录残留。
原因:单文件模式每次运行都要解压整个包到临时目录,杀软还会对解压出来的每个 dll 逐一扫描,这就是启动慢的根源。正常情况下程序退出时 bootloader 会清理临时目录,但如果进程被杀软拦截、系统强制结束进程,_MEI目录就会留在那。
解决:能接受多文件就换 onedir,一劳永逸;坚持单文件的话,把 exe 放进安装包工程里分发,让安装器负责释放,而不是让用户直接面对裸 exe。注意不要在 Python 代码里自己去删_MEI目录,那是 bootloader 管理的,手工删除可能引发文件占用错误,反而更容易残留。
4.3 运行报 missing module 或 ModuleNotFoundError
现象:exe 在开发机跑正常,换台机器提示缺少某个模块,或运行到某个功能时才报ModuleNotFoundError。
原因:代码里有动态 import、字符串形式的模块名,或依赖了pkg_resources的扫描入口机制,PyInstaller 的静态分析没有发现这些依赖。3.2.1 的 hook 覆盖比新版本弱,很多第三方库的新插件机制它都认不出来。
解决:先在本机用python app.py验证代码本身没问题;然后用--hidden-import逐个补,或者到 spec 文件的hiddenimports列表里一次性写全。排查顺序是:先看报错模块名,再确认这个模块在 site-packages 下真实存在,最后决定补 import 还是换打包版本。如果模块名本身没问题但还是找不到,检查是不是sys.path在打包后发生了变化,必要时在代码开头手动把项目根目录加进去。
4.4 在新版 Python 上强制安装 3.2.1 导致各种崩溃
现象:pip install pyinstaller==3.2.1在 Python 3.8+ 上能装成功,但一打包就报 encoding 错误或struct.error,甚至pyinstaller命令直接闪退。
原因:3.2.1 的运行时数据结构没有适配后续 Python 版本的字节码和编码处理,属于版本兼容问题,不是你的代码有问题。很多人在这上面耗大半天,最后发现是打包工具和解释器版本不匹配。
解决:要么把环境换回 Python 3.5/3.6 再打包,要么使用与 Python 版本匹配的 PyInstaller 版本。开发机上需要维护多个 Python 环境并明确标注用途,项目根目录用.python-version或 runtime.txt 这类文件记录解释器版本,避免后来同事用错环境。打包这类事,环境一致性比什么都重要。
4.5 目标机器缺少 VC++ 运行库
现象:exe 在部分 Windows 机器上运行提示“无法定位程序输入点”,或系统错误弹窗显示缺少MSVCP140.dll、VCRUNTIME140.dll。
原因:CPython 在 Windows 上依赖 Visual C++ Redistributable;PyInstaller 会把 Python 解释器相关文件打进去,但不会把 VC 运行库一起打包,除非你手动处理。
解决:在分发说明里写明要求安装 VC++ 运行库,或做一个安装引导,检测注册表里对应版本是否缺失,缺失则自动下载安装。老项目如果是用 VS2015 之前的工具链编译的,注意运行库文件名不同,报错时先看清楚缺的是哪个 dll,再匹配对应的 Redistributable 版本。交付外部客户时,这一项务必要写进环境要求,否则很容易被当成“打包没打好”。
5. 交付前怎么验证:干净环境测试、版本信息和签名技巧
5.1 干净环境验证的步骤和判断标准
打包完成不等于交付完成。我一般会在一个干净的 Windows 虚拟机里做验证:不装 Python、不装开发工具,只按照分发说明安装或不安装 VC 运行库,然后把整个 dist 产物拷进去跑一遍主要功能。重点观察三件事:启动是否正常、临时目录是否在退出后清理、杀软有没有拦截记录。如果用的是 onefile 模式,还要记录一下首次启动耗时,超过五秒就得考虑换 onedir 或优化依赖体积。这个环节能暴露大部分环境依赖问题,比在开发机上跑一百次都管用。
5.2 exe 的版本信息和签名
给 exe 加上版本信息,能让用户在右键属性里看到公司名、产品名和版本号,专业度提升一个档次,也能减少一部分启发式误报。3.2.1 通过 version 文件实现,格式如下:
VSVersionInfo( ffi=FixedFileInfo(filevers=(1, 2, 3, 4), prodvers=(1, 2, 3, 4), mask=0x3f, flags=0x0, OS=0x40004, fileType=0x1, subtype=0x0, date=(0, 0)), kids=[StringFileInfo([ StringTable(u'040904B0', [ StringStruct(u'CompanyName', u'MyCompany'), StringStruct(u'ProductName', u'MyTool'), StringStruct(u'FileDescription', u'Internal Tool'), StringStruct(u'FileVersion', u'1.2.3.4'), StringStruct(u'ProductVersion', u'1.2.3.4')])]), VarFileInfo([VarStruct(u'Translation', [0x0409, 1200])]) )把这个文件存为version_info.txt,在 spec 文件的EXE参数里加上version='version_info.txt',重新打包即可。注意 3.2.1 对 version 文件格式要求比较严格,缩进和逗号按官方示例来,别从新版项目里拷贝,字段对不上会直接报错。签名方面,有条件就用代码签名证书给 exe 做签名,能显著降低 SmartScreen 和杀软的告警概率;没条件时至少把版本信息补上,再配合 onedir 和不上 UPX,误报问题能缓解一大半。
我自己的教训是:早年间在开发机上打包一切正常,交付给客户后被防火墙拦掉,客户甩来一张截图,我连解释的底气都没有。后来凡是交付外部环境的工具,我默认用 onedir、不上 UPX、先确认目标机器上的 VC 运行库,再做一次干净环境验证。这套流程虽然多花十几分钟,但能把“玄学报毒”变成可解释、可处理的问题。希望帮到你。
本文还有配套的精品资源,点击获取