概要
当我们发布 Python 项目时,有时为了隔离运行环境的差异或者不希望以源码的形式发布,一种常用的方法是将 Python 项目打包/编译成二进制文件。打包编译的工具有很多,其中 PyInstaller 和 Nuitka 是目前最主流的两个工具。它们都能将 .py 文件打包成无需安装 Python 环境即可运行的二进制程序,但其底层机制、性能表现和适用场景存在显著差异。
结合实际的项目实践,本文从功能原理、特性异同、性能对比及实际选型建议等详细对比 PyInstaller 与 Nuitka 的核心特性,供参考以根据项目需求做出更合理的技术选型。
简介
PyInstaller
PyInstaller 是一个流行且成熟的 Python 打包工具,能将 Python 脚本及其依赖项(包括解释器、标准库、第三方包等)封装成单个可执行的二进制文件或目录结构。它通过分析字节码来静态追踪导入关系,并将所有必要资源捆绑到运行时环境中。
Nuitka
Nuitka(读作 努伊特卡)是一个真正意义上的 Python 编译器,它将 Python 代码转换为优化的 C/C++ 代码,再通过 gcc/msvc 等编译链编译链接成可执行文件。
Python 项目
提供一个 Python Project,Python 项目 SLOC(一个衡量项目源码量的指标——源码行数,统计时会刨除文档注释、空行、虚拟环境、非.py 文件等无效因素影响)2700+,属于中小型项目(一般代码量处于 1000~10000 的 Python Project 划分为中型项目),不含 GUI 相关依赖,文件结构形如:
|-- main.py
|-- settings.py
|-- framework
|-- basic
|-- release
|-- scripts
|-- cis
|-- venv
打包命令分别为:
pyinstaller -F main.py --workpath .\release\build --distpath .\release\output --clean --specpath .\release\windows
pyinstaller -D main.py --workpath .\release\build --distpath .\release\output --clean --specpath .\release\windows
nuitka --onefile --windows-console=disable --follow-imports --output-dir=.\release\output --clean-cache=all --lto=yes main.py
nuitka --standalone --windows-console=disable --follow-imports --output-dir=.\release\output --clean-cache=all --lto=yes main.py
特性剖析
相同点
- PyInstaller 与 Nuitka 均支持单文件/单目录打包,单文件打包出来的二进制文件相对单目录更'紧凑',但是在运行时单文件的内存开销与单目录不相上下,甚至反超;
- 单文件打包出来的二进制文件,运行时后台会同时驻留至少两个同名的进程(例如编译出来的是 my_app.exe,启动后任务管理器里至少有两个 my_app.exe 进程),其中一个内存开销明显比较小的是打包编译工具夹带的引导器(Bootstrap Loader),负责先把单文件解压到临时目录、设置运行环境(资源解压、环境管理、进程监控、权限管理等)后再启动真正的主进程。所以单文件产物运行时并非真正意义上的'单进程';
- 单文件打包出来的产物如果运行中崩溃,并且使能了进程崩溃后自动重启将会反复在临时目录里解压资源,最后会把磁盘写爆,但单目录产物运行时完全不存在这种问题,因为它目录里放的本身就是解压后的内容,并且运行时不需要,所以相对单文件产物来说运行更稳定,如果不是为了分发方便,更推荐由目录打包编译;
- 对于 Python 项目来说,只要安装了一个第三方模块,不管有没有在项目中 import,都可能会影响到打包编译出来的产物大小,并且一般会使得打包编译的产物变大;(为什么是可能而不是一定,是根据第三方模块而定,像额外安装一个 Cython 就不会影响到打包编译出来的产物,但是安装 airtest / numpy 等就会有影响,即便在项目中没有用到)
差异点
- 不管是打包成单文件还是单目录,PyInstaller 的打包过程都明显比 Nuitka 快,从打包编译的原理上也好理解,后者多了一道先转码再编译的过程;
- Nuitka 编译出来的产物(不管是单文件还是单目录,下同)理论上应该比 PyInstaller 更精简,但是在笔者上面的示例项目中,Nuitka 编译出来的产物比 PyInstaller 大了 ~1.5MB,经分析可能是因为项目并不是特别大,且不含 GUI 或者诸如 OpenCV、paddleocr 等大型第三方依赖,所以 Nuitka 编译的优势没有体现出来;
- 上面提到单文件打包出来的固件运行时会先解压到临时目录,PyInstaller 解压到了 C 盘下的%TEMP%/_MEI* 目录,Nuitka 打包时允许自定义临时目录的位置,但是均解决不了崩溃后资源回收的问题;
- PyInstaller 打包出来的产物可以被反'打包'(可通过 pyinstxtractor 提取 pyc)反编译成 Python 源码,但 Nuitka 几乎不可能实现反编译;
- Nuitka 编译出来的产物运行效率显著优于 PyInstaller 打包出来的产物;
- Nuitka 在打包编译的时候控制更灵活,可以控制编译行为,比如开启性能优化等;
场景选型
- 小型项目(SLOC < 1000),快速交付验证:PyInstaller
- 中型项目(SLOC 1000~10000),带 GUI / 图像处理 / 网络服务
- 大型项目(SLOC > 10000),优选 Nuitka。
总之,如果更注重高性能、安全性、防逆向、运行时资源消耗小、商业化,优选 Nuitka;如果追求快速迭代、生态成熟稳定、兼容性好,优选 PyInstaller。
总结
PyInstaller 与 Nuitka 代表了 Python 打包领域的两种哲学:
- PyInstaller 是'打包专家'——擅长整合现有资源,快速交付可用成果;
- Nuitka 是'编译先锋'——致力于将 Python 提升至系统级语言的执行效率。
对于大多数开发者而言,PyInstaller 是首选入门工具;而对于追求性能、安全与专业形象的团队,Nuitka 正逐渐成为下一代标准。
技术选型没有银弹,只有最适合业务场景的选择。理解工具本质,方能游刃有余。