最近在给团队做内部工具的时候又被 Python 打包这事折腾了一遍。说真的这玩意看起来简单真要让你选个合适的方案没个三五天根本摸不清门道。我团队那帮人java、go、PHP 的都有每次看我搞 Python 部署就笑话我。“你这啥玩意儿打个包搞一下午” 你还别说真就一下午还不一定能搞利索。所以这次我决定把市面上主流的 Python 打包方式都跑一遍每个都写写感受说说坑给大家一个真实参考。写之前我没看任何官方文档全凭自己动手踩坑这样写出来的东西才有烟火气。先说结论没有银弹只有适合。不同场景用不同方案强行用错工具只会让自己加班。那些年我被 Python 打包支配的恐惧先聊聊为啥要写这篇文章。事情是这样的上个月接了个活要把公司一个数据处理脚本做成可执行文件给运营同事用。他们电脑是 windows没装 Python也不会装。我当时心想pyinstaller 一把梭呗结果搞了一下午。为啥因为这脚本依赖了 pandas、numpy、requests还有一堆奇奇怪怪的库。打包出来 300 多 M运营同事传个文件发邮件都不方便。更要命的是第一次在他电脑上跑直接报错说缺什么 dll我人都麻了。后来换了几种方式发现每种都有它自己的脾气。今天就把我试过的几种全讲讲。不过别期待太高Python 打包这个领域本身就是能用和好用之间的巨大鸿沟。你要是想找完美方案那真的没有。PyInstaller这个老朋友我又爱又恨先说 PyInstaller估计 90% 的 Python 开发者都用过它。用起来真的很简单。一行命令搞定pyinstaller -F your_script.py-F 是单文件模式打包出来就一个 exe用户拿着就能跑。对开发者友好度这块PyInstaller 真的没得说。我第一次用的时候感觉这玩意简直是神器。我写了个小工具打包成 exe 发给同事他双击就能用那种成就感懂的人都懂。但坑也多。最典型的就是体积问题。我那个数据处理脚本打包出来 280M。同事问我“你这啥程序啊怎么比英雄联盟客户端还大” 我无言以对。为啥这么大因为 PyInstaller 是把整个 Python 解释器和所有依赖库都塞进去了。pandas、numpy 这些库本身就大再加上 PyInstaller 的运行时动辄上百 M 起步。还有更恶心的兼容性问题。我那次在 Windows 10 打包的 exe拿到 Windows 7 上跑不起来。报什么 “api-ms-win-crt” 错误查了半天是缺 VC 运行库。还有在某些精简版系统上连 tkinter 都跑不起来要手动指定 hidden import。pyinstaller --hidden-importtkinter your_script.py这种坑第一次遇到真的能让人崩溃。不过话说回来PyInstaller 还是我最常用的。因为它跨平台Windows、Linux、macOS 都能打社区也活跃遇到问题基本搜得到。对于一些内部小工具不在乎体积的情况下PyInstaller 仍然是首选。有一次我做个小爬虫工具就一个 py 文件依赖只有 requests 和 beautifulsoup4打包出来才 30M体验就很好。所以体积这事看你依赖什么库。纯 Python 库小带 numpy、pandas 这些科学计算库就炸。cx_Freeze跨平台的另一种选择cx_Freeze 这玩意我接触得比 PyInstaller 少但也有故事。当时做项目要在 Linux 服务器上跑一个 Python 脚本但服务器是个精简版系统连 pip 都没有。我想着搞个绿色版直接拷过去就能用。cx_Freeze 生成的目录结构比较合理library 和可执行文件分开。看着比 PyInstaller 整洁一些。但这玩意用起来比 PyInstaller 麻烦。要写 setup.pyfromcx_Freezeimportsetup,Executable setup(namemyapp,executables[Executable(your_script.py)])然后python setup.py build出一堆文件。配置项多新手容易懵。优点是跨平台做得不错生成的结构清晰库和可执行文件分离对于一些需要定制部署的场景比较友好。缺点就是配置复杂文档也不如 PyInstaller 详细。我那次配置参数搞了半天最后还是放弃了转头用了 PyInstaller。要是你项目有特殊需求比如要打进一些自定义的 .so 文件或者 dllcx_Freeze 的配置反而更灵活。但对普通用户来说没那必要折腾。Nuitka把 Python 编译成 C性能起飞这个是我最近才认真试的用完直接说一句真香。Nuitka 的思路不一样它不是把 Python 解释器和代码一起打包而是把 Python 代码编译成 C 代码然后再编译成机器码。对你没看错是真的编译不是那种伪编译。第一次跑的时候我在 Linux 上测试一个数据处理脚本编译完跑起来速度直接快了一倍。我以为是我眼花了又跑了几次确实快了不少。nuitka --standalone --onefile your_script.py参数和 PyInstaller 类似但生成的二进制文件运行效率高很多。体积方面也很惊艳。同样的脚本PyInstaller 打包 280MNuitka 编译出来 80M。差距不是一点半点。但坑也来了。Nuitka 的编译速度是真的慢。我那个 300 行的脚本编译花了 4 分钟。PyInstaller 几秒钟搞定。Nuitka 几分钟后才有结果。还有就是兼容性。Nuitka 对一些冷门库的支持不如 PyInstaller 好。我有个脚本用了 pyexecjsNuitka 怎么都搞不定最后还是退回 PyInstaller。所以我的建议是如果你的项目对性能有要求Nuitka 真的值得一试。但要是项目复杂、依赖多PyInstaller 还是更稳。Nuitka 还有个加分项它支持 Python 3.6 以上的所有版本而且对一些 C 扩展库的支持做得不错至少 numpy、pandas 这种常见的没问题。但我得提醒一句Nuitka 安装本身就是个坑。在 Linux 上还好pip 直接装就行。在 Windows 上你要先装 C 编译器Visual Studio Build Tools几 G 的东西。不像 PyInstaller 那样 pip install 就能用。源码包与 wheel 包最 Pythonic 的方式说完编译型的再说说 Python 圈子最传统的打包方式——源码包和 wheel 包。这个就是用 setuptools 那一套写个 setup.py 或者 pyproject.toml然后python setup.py sdist bdist_wheel。这玩意严格说不算打包成可执行文件但它在 Python 生态里太重要了不得不提。python setup.py sdist # 源码包 python setup.py bdist_wheel # wheel 包wheel 包的好处装起来快。因为是预编译的格式pip 安装 wheel 包的时候不需要本地编译直接解压就行。我之前在树莓派上装过一个库等编译等了 20 分钟。换 wheel 包几秒钟。这种体验差异真的巨大。wheel 包还有个隐藏好处可以指定平台和 Python 版本。xxx-cp39-cp39-linux_x86_64.whl这名字一看就知道是哪个平台哪个版本用的。源码包sdist则更通用里面就是 Python 源代码和 setup.py。pip 装了之后会自动编译但有些纯 Python 包就直接拷贝了。这两种方式适合的场景你要发布的是库而不是应用。用户拿到你的包后还会 import 使用而不是直接运行。我自己的项目如果是写库给别人用就用这种方式。如果是写工具给自己或者非技术人员用就用 PyInstaller 或者 Nuitka 编译成可执行文件。Docker容器化才是终极方案说完传统的打包方式再聊聊现在云原生时代的玩法——Docker。严格说 Docker 不是 Python 打包工具但用 Docker 部署 Python 应用真的太香了我现在新项目基本都是这套。Dockerfile 写起来简单FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD [python, your_script.py]这一套下来生成的镜像扔到任何装了 Docker 的机器上都能跑。不用管宿主机的 Python 版本不用管系统库依赖不用管这个那个环境变量。优点太多了环境隔离得彻底。每个容器都是独立的 Python 环境互不干扰。部署一致性。开发环境测试过了生产环境肯定一样。这点对运维来说简直是救命的。资源利用高效。一个服务器可以跑几十上百个容器密度比虚拟机高多了。但也有代价镜像体积不小。最小的 python:3.9-slim 镜像也有几十 M加点依赖就上百 M。不过相比 PyInstaller 的 280M还是能接受的。需要懂 Docker。不是说门槛多高但要是团队没人用过 Docker学起来还是要点时间。启动速度比直接跑可执行文件慢。不过这点在生产环境一般不是问题。我个人现在的做法所有线上服务都走 Docker本地开发也尽量用 Docker。桌面端的小工具才用 PyInstaller 这种。Docker 还有一个隐藏的好处可以多阶段构建。编译阶段用一个完整镜像运行阶段用一个精简镜像镜像体积能小很多。比如FROM python:3.9 AS builder WORKDIR /app COPY requirements.txt . RUN pip install --user -r requirements.txt FROM python:3.9-slim WORKDIR /app COPY --frombuilder /root/.local /root/.local COPY . . CMD [python, your_script.py]这样最终的镜像能小一半不止。PyOxidizerRust 写的打包工具性能怪兽这个是最近挖到的宝PyOxidizer。它是用 Rust 写的思路和 Nuitka 类似但更激进——它把 Python 解释器嵌入到一个 Rust 二进制里。听起来很硬核是吧用起来其实没那么复杂# pyoxidizer.bzl [[python_run_file]] path your_script.py然后pyoxidizer build搞定。这个工具的亮点启动速度比 PyInstaller 快得多。Nuitka 我已经觉得很厉害了PyOxidizer 比它还快。我做了个简单测试启动一个简单的 Flask 应用PyInstaller 打包启动 2.3 秒Nuitka 编译启动 1.1 秒PyOxidizer启动 0.4 秒这差距真的不是一点半点。而且它的单文件模式更彻底。PyInstaller 的 -F 模式本质上还是先解压到临时目录再运行PyOxidizer 是真的把解释器和代码都嵌入到一个可执行文件里没有解压过程。但缺点也很明显首先这玩意还比较新文档不算完善很多功能要翻 GitHub Issues 才能搞清楚。其次对 Windows 的支持还在完善中。我在 Windows 上用踩了几个坑最后还是退回 Linux 环境。还有就是配置用的是 Starlark 语法一种 Python 风格的配置语言和 Python 本身有点区别新手要适应一下。我的评价是PyOxidizer 是打包工具的未来但现在还不够成熟。要是你项目允许用 Linux对启动速度有极致要求可以试试。实战案例我是怎么给运营做工具的光说理论太空了给大家讲讲我最近做的真实项目。需求是这样的运营部门每周要处理一批 Excel 文件提取数据生成报表。原来是人工处理一人一天。现在让我搞个自动化工具。第一个版本我用 PyInstaller写了个 Python 脚本用 pandas 处理 Excelopenpyxl 写报表tkinter 做了个简单界面。PyInstaller 打包出来 280M运营同事收到邮件当天就吐槽“这啥玩意这么大”第二个版本我换了 Nuitka编译出来 80M运行速度还快了不少。运营同事勉强接受了。但有个问题他们公司电脑有些是老款 Windows 7Nuitka 编出来的二进制文件跑不起来要装 VC 运行库IT 部门不太愿意装。第三个版本我妥协了用 Docker在内部服务器上起了个 Web 服务运营同事用浏览器访问。镜像大小 200M 左右但在服务器上运行稳定得很。运营同事也不用装任何东西打开浏览器就能用。最后选了 Docker 方案。不是因为它最好而是因为最适合运营同事的使用场景。这个案例给我的最大启发打包方式的选择不是看技术多牛而是看用户场景。给技术人员用和给非技术人员用完全是两个思路。选哪个给你个参考说了这么多到底选哪个我列个表给你参考场景推荐方案理由内部小工具分发给同事PyInstaller简单跨平台对性能有要求Nuitka编译型启动快桌面应用要给非技术人员PyInstaller 或 Web 化学习成本低发布 Python 库setuptools wheel生态标配线上服务Docker部署一致性云原生环境Docker标准化极致启动速度PyOxidizer启动飞快我个人的优先级Docker Nuitka PyInstaller 源码包 PyOxidizer日常开发基本都 Docker性能要求高的脚本用 Nuitka分发小工具才用 PyInstaller。这些坑你别再踩了最后说几个常见的坑新手一定要避开第一不要在打包环境里瞎装东西。我见过有人在打包机器上装了一堆开发工具结果打出来的包巨大依赖混乱。最好用干净的虚拟环境打包pip install 完依赖就打包别多装。第二注意隐藏依赖。有些库是动态导入的PyInstaller 静态分析找不到要在 spec 文件里手动指定 hidden import。这事不复杂但很多人栽在这里。第三路径问题。打包后的程序file这个变量的行为会变。在 PyInstaller 单文件模式下file指向的是临时解压目录不是你的 exe 所在目录。要用 sys._MEIPASS 获取真实路径。第四不要忽略杀毒软件。PyInstaller 打包的 exe 经常被杀软误报尤其是国内各种管家。我之前有个工具运营同事的电脑装了 360直接给拦截了还给我归到高危。这种情况要么加白名单要么换签名要么用 Web 方案。第五不要忽视更新机制。PyInstaller 打包的 exe 没法自动更新要重新打包让用户下载。Docker 容器可以拉取最新镜像Web 应用根本不用管。这点对工具的长期维护影响很大。写在最后Python 打包这事说简单也简单说复杂也复杂。简单是因为大部分情况下 PyInstaller 就能搞定复杂是因为真要做得完美需要考虑的场景太多。我没有最好的方案给你只有最适合的方案。选择之前想清楚三件事用户是谁技术人员还是非技术人员部署环境是什么Windows、Linux、还是云端维护成本怎么算频繁更新还是一锤子买卖想清楚这三个问题答案自然就有了。Python 打包这领域未来肯定会越来越好。PyOxidizer 让我看到了一些希望Nuitka 也在快速迭代。但现阶段要学会将就着用别追求完美。行了今天就聊这么多。下次有机会讲讲 Python 项目结构怎么组织那个也是个大坑。