Nuitka 的包配置系统#
我们来一起用华罗庚先生提出的“统筹方法”来探索 Nuitka 包配置的学习路径。华罗庚的学习法强调:
抓住关键:找准问题的核心,集中突破;
化繁为简:通过归纳、类比、抽象,把复杂问题简化;
由浅入深:从基础出发,逐步深入;
举一反三:学会迁移和类推,形成系统思维。
我们可以把 Nuitka 包配置的内容分成几个“关键点”,逐步探索:
探索 Nuitka 包配置的学习路径#
👉第一步:抓住核心问题 —— Nuitka 为什么需要包配置?#
Nuitka 是一个将 Python 代码编译为 C/C++ 的编译器,最终生成可执行文件。这种编译方式带来了性能提升和部署便利,但也引发了一些兼容性挑战,尤其是对第三方包的支持。下面列出几个关键问题:
依赖 DLL 或数据文件:某些 Python 包(如图像处理、科学计算类库)依赖外部 DLL 或数据文件。如果这些文件在编译后未被正确包含,程序运行时就会报错。
隐式导入:有些包在运行时动态导入模块(如通过
__import__或插件机制),这些模块在静态分析时可能被遗漏,导致编译后的程序缺少必要组件。依赖膨胀:某些模块会引入大量不必要的依赖,增加编译体积,影响性能。
平台或 Python 版本差异:不同操作系统或 Python 版本下,某些模块行为不同,可能需要特殊处理。
思考
你觉得这些问题中,哪个最值得先解决?
👉第二步:化繁为简 —— 用 YAML 文件统一管理#
Nuitka 用 .nuitka-package.config.yml 文件来集中管理这些问题。每个模块的配置都可以写成类似这样:
# yamllint disable rule:line-length
# yamllint disable rule:indentation
# yamllint disable rule:comments-indentation
# too many spelling things, spell-checker: disable
---
- module-name: 'pandas._libs' # 解决 pandas._libs 模块的依赖问题
implicit-imports:
- depends:
- 'pandas._libs.tslibs.np_datetime'
- 'pandas._libs.tslibs.nattype'
这就是“统筹方法”的体现:把分散的问题集中处理,用统一格式解决不同类型的兼容问题。
备注
在文件的开头,您会发现以下几行,您可以忽略它们,它们基本上只是为了让检查器忽略那些难以避免的问题。
# yamllint disable rule:line-length
# yamllint disable rule:indentation
# yamllint disable rule:comments-indentation
# too many spelling things, spell-checker: disable
---
思考
你能看出这个配置解决了什么问题吗?
👉第三步:由浅入深 —— 分类学习配置项#
可以把配置项分成几类,逐个突破:
配置项类型 |
作用说明 |
|---|---|
data-files |
指定要复制的文件或目录 |
dlls |
指定要包含的 DLL 文件 |
implicit-imports |
指定模块的隐式依赖 |
anti-bloat |
去除不必要的代码或依赖 |
options |
设置模块运行所需的选项 |
import-hacks |
处理特殊导入行为 |
思考
你想先从哪一类开始深入?
后续内容逐步分析每个字段的含义和用法。
👉第四步:举一反三 —— 自定义配置与贡献#
你可以写自己的配置文件并通过 --user-package-configuration-file=xxx.yml 使用它。如果你发现某个包的通用解决方案,也可以提交 PR 贡献给 Nuitka 社区。
🧱 data-files 字段结构解析#
data-files 字段用于告诉 Nuitka 在编译时将哪些数据文件复制到最终的输出目录中。它支持以下几种配置方式:
字段名 |
作用 |
|---|---|
|
指定目标路径,默认是当前模块目录( |
|
要复制的整个目录列表 |
|
要匹配的文件名或通配符(如 |
|
要创建的空目录列表 |
|
要创建的空目录结构(用于更复杂的场景) |
|
条件表达式,控制在哪些平台或模式下启用(如 |
📦 示例解析与应用场景#
✅ 示例 1:复制一个数据目录#
- module-name: 'customtkinter'
data-files:
dirs:
- 'assets'
📌 应用场景
你的包中有一个 assets 文件夹,里面包含图标、样式等资源。这个配置会将整个目录复制到编译后的输出中。
✅ 示例 2:复制架构相关的目录(但有局限)#
- module-name: 'tkinterweb'
data-files:
dirs:
- 'tkhtml'
📌 应用场景
你需要将 tkhtml 文件夹打包进去,但注意:如果文件内容依赖架构(如 32 位或 64 位),这种方式可能不够精确。
✅ 示例 3:创建空目录#
- module-name: 'Crypto.Util._raw_api'
data-files:
empty_dirs:
- '.'
📌 应用场景
某些包运行时会通过 __file__ 推导目录路径,如果目录不存在就会报错。这个配置确保目录存在,即使里面没有文件。
🧠 小练习:你来判断#
假设你正在编译使用 transformers 的项目,它需要:
加载一个模型文件
bert-base.bin(位于models/目录)读取一个配置文件
config.json(位于configs/目录)创建一个空目录
cache/用于运行时缓存
👉 请你试着写出一个 data-files 配置片段,包含这三项需求。你可以先写出一部分,我来帮你检查和补充。准备好了吗?
点击查看解析
- module-name: 'transformers'
data-files:
dirs:
- 'models'
- 'configs'
empty_dirs:
- 'cache'
✅ 配置解析
module-name: 'transformers'
👉 表示这个配置是针对transformers包的。dirs: ['models', 'configs']
👉 Nuitka 会将这两个目录中的所有内容复制到编译后的输出目录中。适用于模型文件(如.bin)和配置文件(如.json)。empty_dirs: ['cache']
👉 Nuitka 会创建一个名为cache的空目录,确保运行时不会因为目录缺失而报错。
🧠 延伸思考:是否需要 dest_path
如果你没有写 dest_path,这其实是个好习惯!因为:
默认目标路径是
.,即相对于包目录。只有在某些特殊情况下(比如包运行时硬编码了某个路径),才需要手动指定
dest_path。
dlls#
👉 有些模块在运行时动态加载 DLL 文件,而不是通过扩展模块(如 .pyd)静态链接。这种情况下,Nuitka 无法自动检测这些 DLL,因此你必须在配置文件中显式指定它们。
存在两种方式来指定 DLLs:
✅ 方式一:from_filenames(推荐方式)#
dlls:
- from_filenames:
relative_path: 'dlls'
prefixes:
- 'dll1'
- 'mydll*'
suffixes:
- 'pyd'
dest_path: 'output_dir'
when: 'win32'
relative_path: DLL 所在目录,相对于模块路径。prefixes: DLL 文件名前缀,可以用通配符。suffixes: 文件后缀(如.dll,.pyd),用于精确匹配。dest_path: DLL 复制到的目标目录。when: 条件表达式,控制在哪些平台或架构下启用。
📎这种方式是推荐的,因为它更稳定、易维护。
⚠️ 方式二:by_code(实验性)#
dlls:
- by_code:
setup_code: ''
filename_code: ''
dest_path: 'output_dir'
when: 'linux'
setup_code: 编译前执行的准备代码。filename_code: 生成 DLL 文件名的代码。
📎 这种方式依赖编译时执行代码,容易出错,目前仍在改进中。
你贴出的内容是 Nuitka 包配置文档中关于 DLL 文件处理的三个典型示例,我们来逐个解释它们的结构和用途,并结合页面内容理解它们背后的设计思路。
🧩 DLL 配置示例详解#
✅ 示例 1:最简单的情况 —— 直接从包目录复制 DLL#
- module-name: 'vosk'
dlls:
- from_filenames:
prefixes:
- 'libvosk'
📌 说明:
vosk包中包含名为libvosk*.dll的文件。Nuitka 会在该模块目录下查找以
libvosk开头的 DLL 文件并打包进去。没有指定
relative_path,默认就是模块目录。
📎 这是最常见、最推荐的方式,简单且稳定。
✅ 示例 2:架构相关的 DLL 文件在子目录中#
- module-name: 'tkinterweb'
dlls:
- from_filenames:
relative_path: 'tkhtml/Windows/32-bit'
prefixes:
- 'Tkhtml'
when: 'win32 and arch_x86'
- from_filenames:
relative_path: 'tkhtml/Windows/64-bit'
prefixes:
- 'Tkhtml'
when: 'win32 and arch_amd64'
📌 说明:
tkinterweb包的 DLL 文件根据架构(32位或64位)存放在不同子目录中。使用
relative_path指定子目录路径。使用
when条件表达式确保只在对应平台和架构下启用。
📎这种方式适用于架构敏感的 DLL 文件,避免错误打包。
✅ 示例 3:根据平台选择不同后缀的 DLL 文件#
- module-name: 'tls_client.cffi'
dlls:
- from_filenames:
relative_path: 'dependencies'
prefixes:
- 'tls-client'
suffixes:
- 'dll'
when: 'win32'
- from_filenames:
relative_path: 'dependencies'
prefixes:
- 'tls-client'
suffixes:
- 'so'
when: 'linux'
- from_filenames:
relative_path: 'dependencies'
prefixes:
- 'tls-client'
suffixes:
- 'dylib'
when: 'macos'
📌 说明:
tls_client.cffi包在不同平台使用不同后缀的动态库文件。所有 DLL 都在同一个目录
dependencies中。使用
suffixes区分平台专属文件类型(Windows 用.dll,Linux 用.so,macOS 用.dylib)。使用
when精准控制平台条件。
📎 这种方式可以避免打包多个平台的 DLL 文件,确保只包含当前平台所需的内容。
🧠 总结#
这些示例体现了 Nuitka 的 DLL 配置设计原则:
精确匹配:通过
prefixes和suffixes控制文件名。路径控制:通过
relative_path指定 DLL 所在目录。平台适配:通过
when条件表达式实现跨平台兼容。推荐方式:页面明确建议使用
from_filenames,而不是by_code,因为前者更稳定、易维护。
这段话出现在你正在浏览的 Nuitka 包配置文档中,属于对 EXEs 配置项的说明。它解释了 Nuitka 如何处理可执行文件(EXE)与 DLL 文件的关系,并提供了配置方法。我们来逐句拆解,并结合页面内容深入理解它的含义。
✅EXE 配置#
dlls:
- from_filenames:
prefixes:
- 'subprocess'
executable: 'yes'
- from_filenames:
prefixes:
- '' # first match decides
✅ 第一项:
prefixes: ['subprocess']:表示查找以subprocess开头的文件。executable: 'yes':告诉 Nuitka,这个文件是一个 EXE,而不是普通 DLL。
⚠️ 第二项:
prefixes: ['']:空前缀表示匹配第一个找到的文件。
🧠 总结:
Nuitka 把 EXE 文件当作特殊的 DLL 文件处理。
默认 DLL 是不可执行的,只有加上
executable: yes才会被当作 EXE。这种设计让 DLL 和 EXE 的配置方式保持一致,简化了打包逻辑。
🧹 Anti-Bloat:去除不必要的依赖或代码#
👉 如果你想在编译时修改某些模块的代码,比如去掉测试代码、日志模块或不必要的依赖,可以使用 anti-bloat 配置项。
字段功能如下:
字段名 |
作用 |
|---|---|
|
描述这个去膨胀操作的目的 |
|
指定在哪个上下文中应用(通常为空) |
|
替换整个模块的代码 |
|
用字符串替换方式修改代码 |
|
用正则表达式替换代码 |
|
用表达式替换代码 |
|
替换某个函数的定义,如设为 |
|
向模块追加代码 |
|
条件表达式,控制在哪些平台或版本下启用 |
📎这种方式可以用来精简打包体积、避免不必要的依赖,尤其适用于大型包如 matplotlib、scipy。
🧠 示例分析#
- description: 'remove tests'
module_code: 'from hello import world'
change_function:
'get_extension': 'un-callable'
📌 说明:
将整个模块替换为一行代码
from hello import world。把
get_extension函数设为不可调用(un-callable),防止它被执行。
🔍 Implicit-Imports:处理动态导入的模块#
👉 有些包在运行时动态导入模块(比如插件机制),Nuitka 静态分析时无法识别这些依赖。你可以用 implicit-imports 显式声明它们。
字段功能如下:
字段名 |
作用 |
|---|---|
|
指定该模块依赖的其他模块 |
|
在导入前后执行的代码 |
|
告诉 Nuitka 不要自动跟踪某些导入 |
|
条件表达式,控制在哪些平台或版本下启用 |
🧠 示例分析#
✅ cv2 的隐式依赖:#
- module-name: 'cv2'
depends:
- 'cv2.cv2'
- 'numpy'
- 'numpy.core'
pre-import-code:
- |
import os
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.join(os.path.dirname(__file__), 'qt/plugins')
os.environ['QT_QPA_FONTDIR'] = os.path.join(os.path.dirname(__file__), 'qt/fonts')
when: 'linux and standalone'
📌 说明:
显式声明
cv2依赖的模块。在导入前设置环境变量,确保 Qt 插件和字体路径正确。
✅ tqdm.std 的去膨胀配置:#
- module-name: 'tqdm.std'
anti-bloat:
- no-auto-follow:
'pandas': 'ignore'
📌 说明:
tqdm会尝试注册pandas的方法,但如果pandas没有安装也不会报错。所以可以告诉 Nuitka:不要因为
tqdm导入了pandas就自动打包pandas。
你贴出的内容来自 Nuitka 包配置文档的两个部分:Options(选项提示) 和 Import-Hacks(导入黑科技)。我们来逐句解释它们的含义,并结合页面内容理解它们在实际打包过程中的作用。
⚙️ Options:模块运行所需的编译选项提示#
👉 如果某个模块在特定平台或模式下需要特定的编译选项(比如必须启用 GUI 模式或打包为 macOS bundle),你可以在这里声明这些要求。Nuitka 会在编译时发出提示或警告,帮助用户避免运行时崩溃。
🧰 示例解析#
- module-name: 'wx'
options:
checks:
- description: 'wx will crash in console mode during startup'
console: 'yes'
when: 'macos'
- description: 'wx requires program to be in bundle form'
macos_bundle: 'yes'
when: 'macos'
📌 说明:
wx是一个 GUI 框架,在 macOS 上如果以控制台模式运行会直接崩溃。所以配置中要求:
必须启用 GUI 模式(
console: yes)必须打包为 macOS bundle(
macos_bundle: yes)
📎 这些配置不会自动修改编译行为,而是提示用户必须设置这些选项,否则程序可能无法运行。
🧠 字段功能总结#
字段名 |
作用 |
|---|---|
|
描述这个选项的目的 |
|
是否启用控制台窗口(yes/no/recommend) |
|
是否打包为 macOS 应用(yes/no/recommend) |
|
是否将 bundle 打包为单文件 |
|
提示级别(info/warning/error) |
|
条件表达式,控制在哪些平台或模式下启用 |
🧪 Import-Hacks:解决特殊导入行为#
👉 有些包会在运行时修改 sys.path,或使用 Nuitka 无法静态分析的导入方式。你可以用 import-hacks 显式告诉 Nuitka 如何处理这些导入。
🧰 示例解析#
- module-name: 'tkinterweb'
import-hacks:
- global-sys-path:
- ''
📌 说明:
tkinterweb包中有如下代码:sys.path.append(os.path.dirname(os.path.realpath(__file__)))
这会把包目录加入全局导入路径,使得包内模块可以被绝对导入。
Nuitka 编译时无法自动识别这种行为,所以需要通过
global-sys-path显式声明:将当前目录加入导入路径。
📎这种 hack 很少见,但对某些包是必须的。
你贴出的内容来自 Nuitka 包配置文档的 Variables(变量) 和 Constants(常量) 部分,它们是 Nuitka 配置系统中非常强大的机制,用于在编译时动态获取包信息,并在配置中灵活使用。我们来逐句解析,并结合页面内容理解它们的作用和使用方式。
🧠 Variables:编译时动态获取信息#
👉 你可以在配置文件中使用变量来获取包的运行信息,比如版本、模块属性等,并在 when 条件或其他字段中使用。
👉 所有变量都通过 get_variable("变量名") 来访问,Nuitka 会记录和缓存这些变量的使用情况。
🧰 示例解析#
variables:
setup_code: 'import whatever'
declarations:
'variable1_name': 'whatever.something()'
'variable2_name': 'whatever.something2()'
📌 说明:
setup_code:在编译时执行一次,用于准备变量获取的上下文。declarations:定义变量名和对应的表达式。使用方式:在其他配置字段中通过
get_variable("variable1_name")引用。
🔒 Constants:跨平台或通用值的定义#
👉 常量用于定义跨平台的值,比如路径后缀、平台标识等,避免在多个地方重复写 when 条件。
🧰 示例解析#
✅ 示例 1:定义平台后缀#
constants:
- declarations:
'suffix': '_Windows'
when: "win32"
- declarations:
'suffix': '_Linux'
when: "linux"
- declarations:
'suffix': '_MacOS'
when: "macos"
implicit-imports:
depends:
- '"package_name_%s" % get_variable("suffix")'
📌 说明:
根据平台定义不同的后缀。
在
depends中动态拼接模块名,实现平台适配。
✅ 示例 2:Torch 包的模块筛选#
constants:
declarations:
'torch_config_module_candidates': '[m for m in iterate_modules("torch") if m.split(".")[-1] in ("config", "_config")]'
variables:
setup_code: 'import importlib'
declarations:
'torch_config_modules': 'dict((m,importlib.import_module(m)._compile_ignored_keys) for m in torch_config_module_candidates if hasattr(importlib.import_module(m), "_compile_ignored_keys"))'
📌 说明:
使用
iterate_modules("torch")获取所有子模块。通过常量筛选出以
.config或._config结尾的模块。使用变量导入这些模块并提取
_compile_ignored_keys属性。
你贴出的内容来自 Nuitka 包配置文档的“表达式与条件判断”部分,它详细介绍了如何在配置文件中使用布尔表达式、平台判断、版本检测等机制来控制配置项的启用。我们来逐步拆解,并结合页面内容理解它的用途和实际写法。
🧠 表达式(Expression):控制配置启用的核心机制#
📌 示例表达式#
when: "macos and python3_or_higher"
📌 含义:
仅当当前操作系统是 macOS 且 Python 版本为 3 或更高时,才启用该配置项。
这是 Nuitka 配置中最常见的条件控制方式,适用于
data-files、dlls、anti-bloat、options等字段。
🧩 可用变量分类#
✅ 操作系统判断(OS Indications)#
变量名 |
含义 |
|---|---|
|
当前平台是 macOS |
|
当前平台是 Windows |
|
当前平台是 Linux |
✅ 编译模式判断(Compilation Modes)#
变量名 |
含义 |
|---|---|
|
是否启用了独立打包模式(包括 onefile 和 app) |
|
是否启用了单文件打包模式 |
|
是否启用了模块模式 |
|
是否启用了部署模式 |
|
是否启用了 onefile 缓存机制 |
📎 大多数配置是针对 standalone 模式的,onefile 只在特殊情况下使用。
✅ Python 版本判断(Python Versions)#
变量名 |
含义 |
|---|---|
|
是否是 Python 2 |
|
是否是 Python 3 或更高版本 |
|
是否是 Python 3.10 或更高版本 |
|
是否是 Python 3.10 之前的版本 |
✅ Python 发行版判断(Python Flavors)#
变量名 |
含义 |
|---|---|
|
是否是 Anaconda Python(不建议直接使用) |
|
是否是 Debian 系统自带的 Python |
📎 建议:使用 is_conda_package("包名") 来判断某个包是否来自 conda,而不是判断整个 Python 环境。
✅ 包版本判断(Package Versions)#
version("rich") is not None and version("rich") >= (10, 2, 2)
📌 含义:
如果
rich包已安装,且版本不低于 10.2.2,则启用该配置。
✅ Anti-Bloat 插件变量#
变量名 |
含义 |
|---|---|
|
是否启用了 setuptools |
|
是否启用了 pytest |
|
是否启用了 unittest |
|
是否启用了 IPython |
|
是否启用了 dask |
📎 这些变量主要用于 anti-bloat 配置,但也可以在其他地方使用。
✅ 其他辅助函数#
get_variable("变量名"):用于访问在variables中定义的变量。experimental("flag-name"):用于判断是否启用了某个实验性功能。iterate_modules("包名"):列出某个包下的所有子模块。
✅ 总结#
Nuitka 的配置系统支持丰富的条件表达式,帮助你根据平台、版本、模式等动态启用配置。
所有表达式都写在
when:字段中,语法接近 Python 的布尔表达式。页面强调:这些表达式不需要执行实际代码,只是用于条件判断,安全且高效。