解决Nuitka打包Pywebview项目的坑
Nuitka + pywebview 打包踩坑记:从 OOM 到 ImportError 的完整修复
背景
最近用 pywebview 写了个桌面端表情包管理工具 OhMyMeme,功能很简单:系统托盘 + 全局热键 + HTML 弹窗搜索/复制。打包时选用了 Nuitka,毕竟它编译出来的 exe 是真正的原生二进制,不像 PyInstaller 那样只是解压一个归档。
结果一路踩了几个坑,其实每个坑都值得单独写一篇。
环境
- Windows 11 x64
- Python 3.12.4
- Nuitka 4.1.3 (Free)
- pywebview 6.2.1
坑一:--onefile 模式下 zstd Out of Memory
现象
第一次打包,兴冲冲地上了 --onefile:
1
python -m nuitka --standalone --onefile --enable-plugin=tk-inter --msvc=latest src/
编译 1244 个模块全部通过,最后压缩阶段炸了:
1
ZstdError: zstd compress error: Allocation error : not enough memory
生成的 .dll 大约 61MB,payload blob 约 13MB。zstd 试图压缩时内存不足。
原因
--onefile 模式下,Nuitka 会把所有编译产物 + 数据文件打包成一个单文件。最终阶段用 zstd 压缩 payload,对于 60MB+ 的二进制,zstd 需要大量内存完成压缩。在 32GB 内存的机器上依然触发,说明这不是物理内存不足,而是 zstd 的 32 位进程地址空间限制或 Nuitka 内部 buffer 设置问题。
解决
放弃 --onefile,改用 --onedir(Nuitka 默认模式)。目录模式下不需要压缩步骤,直接生成文件夹 + exe 的部署结构:
1
python -m nuitka --standalone --onedir ...
运行 python scripts/build.py --onedir 即可。如果你非要单文件且内存足够,可以试试商业版 Nuitka,它的压缩实现不同。
坑二:pywebview 插件硬编码 Allowlist 遗漏 webview.platforms.win32
现象
--onedir 构建成功,但 exe 运行时报错:
12
ImportError: Module 'webview.platforms.win32' was actively excluded
from Nuitka compilation.
更诡异的是,加上 --enable-plugin=pywebview 也没用。
根因
打开 Nuitka 的 pywebview 插件源码一探究竟:
C:\Python312\Lib\site-packages\nuitka\plugins\standard\PywebViewPlugin.py:41-47:
12345678910
def decide_module(self, module_name, ...):
if module_name.startswith("webview.platforms"):
result = module_name in (
"webview.platforms.winforms",
"webview.platforms.edgechromium",
"webview.platforms.edgehtml",
"webview.platforms.mshtml",
"webview.platforms.cef",
)
return result
这是一个硬编码的白名单,插件根据模块名是否在列表中决定是否编译。问题在于:这个列表是在 pywebview v5 时代写的,当时还没有 webview.platforms.win32 模块。
pywebview 6.x 对 Windows 平台做了重构,把 Win32 API 相关功能抽到了独立的 webview/platforms/win32.py 中。winforms.py 第 22 行:
1
from webview.platforms import win32
Nuitka 插件检查 webview.platforms.win32 → 不在白名单 → return False → 模块被排除 → winforms 导入失败 → pywebview 认为 pythonnet 未安装 → 炸。
尝试过的无效方案
| 方案 | 结果 |
|---|---|
--enable-plugin=pywebview |
插件逻辑没变,依然排除 win32 |
--include-module=webview.platforms.win32 |
用户要求 include vs 插件要求 exclude → Fatal Conflict |
--no-deployment-flag=excluded-module-usage |
不再报 excluded,但模块根本没被编译,变成 cannot import name 'win32' |
最终解决
正确的做法是:禁用这个有 bug 的插件,手动指定所有需要的 platform 模块。
123456
--disable-plugin=pywebview
--include-module=webview.platforms.win32
--include-module=webview.platforms.winforms
--include-module=webview.platforms.edgechromium
--include-module=webview.platforms.mshtml
--include-module=webview.platforms.cef
--disable-plugin=pywebview 完全禁用 pywebview 插件,避免它的硬编码白名单干扰。然后 --include-module 逐个告诉 Nuitka 需要编译哪些 platform 模块。
后续
我向Nuitka项目提交了PR,已被合并。(顺手还修了个bug:项目将“==”写成了“=”,导致无论如何Qt都会被打包进去)
最终的完整构建命令
12345678910111213141516
python -m nuitka --standalone ^
--output-filename=OhMyMeme ^
--python-flag=nosite --python-flag=-m ^
--enable-plugin=tk-inter ^
--disable-plugin=pywebview ^
--include-module=webview.platforms.win32 ^
--include-module=webview.platforms.winforms ^
--include-module=webview.platforms.edgechromium ^
--include-module=webview.platforms.mshtml ^
--include-module=webview.platforms.cef ^
--include-data-dir=src/webui=src/webui ^
--include-package=PIL --include-package=pystray ^
--include-package=pyperclip --include-package=cryptography ^
--include-package=keyboard --include-package=bottle ^
--windows-disable-console --msvc=latest ^
src/
经验总结
Nuitka 插件不一定可靠。插件的白名单可能滞后于依赖库的版本更新。遇到模块被错误排除时,检查对应插件的
decide_module实现。--include-modulevs 插件冲突:用户显式要求 include 的模块如果被插件 exclude,Nuitka 会报 Fatal Conflict。解决方案是--disable-plugin=xxx禁用有问题的插件。--no-deployment-flag=excluded-module-usage的局限性:这个 flag 只是让 Nuitka 不把排除当错误,但不会让模块被编译。模块文件依然不会出现在 dist 目录中。--onedir优先于--onefile:除非你的程序很小(< 20MB),否则--onefile的 zstd 压缩阶段容易 OOM。--onedir没有压缩步骤,更可靠,更新时也只需要替换 exe 本身。动态导入是编译器的天敌:
importlib.import_module()这种动态导入对任何静态编译器(Nuitka、Cython、PyInstaller)都是挑战。打包前用grep -r "import_module\|__import__"扫描一下代码,提前处理。
附:OhMyMeme 项目
项目地址:https://github.com/TNTXZ/OhMyMeme
一个轻量级表情包管理工具:系统托盘常驻、全局热键呼出、HTML 搜索/标签/收藏、一键复制到剪贴板、FTP 同步。
构建脚本见 scripts/build.py,已包含上述所有修复。
