内容

Nuitka 用户手册

内容

Nuitka 用户手册

概览

如果你对使用 Nuitka 感兴趣,建议首先阅读这份文档,了解它的使用情况,检查你可以期待什么,许可证,要求,信用等等。

Nuitka is the Python compiler. It is written in Python. It is a seamless replacement or extension to the Python interpreter and compiles every construct that CPython 2.6, 2.7, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 have, when itself run with that Python version.

然后它以一种极其兼容的方式将未编译的代码和已编译的代码一起执行。

你可以自由使用所有 Python 库模块和所有扩展模块。

Nuitka 将 Python 模块翻译成 C 级程序,然后使用 libpython 和自己的静态 C 文件,以 CPython 的方式执行。

所有的优化都是为了避免开销,在没有必要的地方。没有任何优化是为了消除兼容性,虽然偶尔会做一些轻微的改进,但并不是标准 Python 的每一个错误都被模拟,例如给出更完整的错误信息,但有一个完全的兼容模式来禁用这些错误。

语法

需求

  • C 编译器。你需要一个支持 C11 或 C++03 的编译器 1

    目前这意味着,你需要使用下面这些编译器:

    • Windows 上的 MinGW64 C11 编译器,必须基于 gcc 11.2 或更高。如果没有找到可用的 C 编译器,它将被自动下载,这是推荐的安装方式,因为 Nuitka 也会为你升级它。

    • Windows 2 上的 Visual Studio 2022 或更高版本,旧版本可以工作,但只支持商业用户。配置使用英语语言包以获得最佳效果(Nuitka 过滤掉垃圾输出,但只针对英语语言)。如果安装了,它将被默认使用。

    • 在所有其他平台上,至少使用 5.1 版的 gcc 编译器,低于此版本的 g++ 编译器至少使用 4.4 版作为替代。

    • macOS X 和大多数 FreeBSD 架构上的 clang 编译器。

    • 在 Windows 上,如果 Visual Studio 安装程序提供,可以使用 Windows 上的 clang-cl 编译器”

  • Python: Version 2.6, 2.7 or 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10

    对于 Python 3.3/3.4 和只有这些,我们需要其他 Python 版本作为 编译时 的依赖。

    Nuitka 本身与所有列出的版本完全兼容,但 Scons 作为内部使用的工具却不兼容。

    对于这些版本,你 需要 也安装 Python2 或 Python3.5 或更高的版本,但只在编译时才需要。这是为了与 Scons(协调 C 语言编译)一起使用,它不支持与 Nuitka 一样的 Python 版本。

    此外,在 Windows 上,不能使用 Python2,因为 clcache 不与它一起工作,需要安装 Python 3.5 或更高版本。

    Nuitka 找到这些需要的 Python 版本(在 Windows 上通过注册表),只要它们被安装,你就不应该注意到它。

    将二进制文件转移到其他机器上

    创建的二进制文件可以独立于 Python 的安装,使用 --standalone--onefile 选项,使之可执行。

    二进制文件名后缀

    创建的二进制文件在 Windows 上有一个 .exe 的后缀。在其他平台上,它们没有独立模式的后缀,或 .bin 后缀,你可以自由删除或改变,或用 -o 选项指定。

    添加加速模式的后缀只是为了确保原始脚本名称和二进制名称不会发生冲突,所以我们可以安全地进行覆盖而不破坏原始源文件。

    必须 是 CPython,Anaconda Python。

    你需要标准的 Python 实现,称为 “CPython”,来执行 Nuitka,因为它与它的实现细节紧密相连。

    它不能从 Windows 应用商店获得。

    众所周知,Windows 应用商店的 Python 肯定不工作,它被检查过。而在 macOS 上,”pyenv” 可能不会工作。

  • 操作系统:Linux、FreeBSD、NetBSD、macOS X 和 Windows(32/64 位)。

    其他的也可能起作用。预计可移植性总体上是好的,但如 Scons 的使用可能要进行调整。确保与 Windows 的 Python 和 C 编译器架构相匹配,否则你会得到神秘的错误信息。

  • 架构:x86、x86_64(amd64)和 arm,可能还有更多的架构

    其他架构预计也能工作,开箱即用,因为 Nuitka 一般不使用任何硬件特性。这些只是经过测试的、已知的好东西。我们欢迎反馈。一般来说,Debian 支持的架构也可以被认为是好的,并且经过测试。

1

使用 gcc 5.x 或更高版本或任何 clang 版本,对这个 C11 的支持是必然的。

MSVC 的编译器还没有做到这一点。但作为一种变通方法,由于 C++03 语言标准与 C11 非常重合,然后在 C 编译器太旧的地方用它代替。Nuitka 在过去需要一个 C++ 编译器,但它改变了。

2

https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs.aspx 免费下载(社区版可以正常使用)。

建议使用最新版本,但不是必须的。另一方面,不需要除 Windows 10 之前的支持,它们可能对你有用,但对这些配置的支持只提供给商业用户。

命令行

推荐执行 Nuitka 的方式是 <the_right_python> -m nuitka,以绝对确定你使用的是哪种 Python 解释器,这样更容易与 Nuitka 的内容相匹配。

其次是执行裸 Nuitka 的最佳方式,即从源码签出或存档,不需要改变环境变量,最值得注意的是,你完全不必为 Nuitka 弄乱 PYTHONPATH。你只需直接执行 nuitkanuitka-run 脚本,不需要对环境做任何改变。你可能想把 bin 目录添加到你的 PATH 中,以方便你使用,但这一步是可选的。

此外,如果你想用正确的解释器执行,在这种情况下,一定要执行 <the_right_python> bin/nuitka,就可以了。

挑选合适的解释器

如果你遇到一个 SyntaxError,你绝对是为你正在编译的程序选择了错误的解释器。

Nuitka 有一个 --help 选项来输出它能做什么。

nuitka --help

nuitka-run 命令与 nuitka 相同,但有一个不同的默认值。它试图编译和直接执行一个 Python 脚本:

nuitka-run --help

这个不同的选项是 --run,并在第一个非选项之后向创建的二进制文件传递参数,所以它与普通的 python 会做的事情有些类似。

安装

For most systems, there will be packages on the download page of Nuitka. But you can also install it from source code as described above, but also like any other Python program it can be installed via the normal python setup.py install routine.

许可证

Nuitka is licensed under the Apache License, Version 2.0; you may not use it except in compliance with the License.

You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

教程设置和在 Windows 上构建

这是基本步骤,如果你什么都没有安装,当然如果你有任何一个零件,就跳过它。

设置

安装 Python

  • Download and install from https://www.python.org/downloads/windows

  • Select one of Windows x86-64 web-based installer (64 bits Python, recommended) or x86 executable (32 bits Python) installer.

  • Verify using command python --version.

安装 Nuitka

  • python -m pip install nuitka

  • 使用命令 python -m nuitka --version 进行验证。

编写一些代码并进行测试

为 Python 代码创建一个文件夹

  • mkdir HelloWorld

  • 创建名为 hello.py 的 Python 文件

def talk(message):
    return "Talk " + message


def main():
    print(talk("Hello World"))


if __name__ == "__main__":
    main()

测试你的程序

像平常那样做。在工作不正确的代码上运行 Nuitka,并不容易调试。

python hello.py

采用以下方式构建

python -m nuitka hello.py

备注

这将提示你下载一个 C 语言缓存工具(以加速重复编译生成的 C 代码)和一个基于 MinGW64 的 C 语言编译器,除非你已经安装了合适的 MSVC。对这两个问题都说 yes

运行它

执行在 hello.py 附近创建的 hello.exe

分发

要分发,用 --standalone 选项构建,这不会输出一个单一的可执行文件,而是整个文件夹。将生成的 hello.dist 文件夹复制到另一台机器上并运行它。

你也可以尝试 --onefile,它确实创建了一个单一的文件,但在转向它之前,要确保单纯的独立运行,因为它将使调试更加困难,例如在数据文件丢失的情况下。

用例

用例 1 - 嵌入所有模块的程序编译

如果你想递归地编译整个程序,而不是只编译作为主程序的单个文件,可以这样做:

python -m nuitka --follow-imports program.py

备注

有比 --follow-imports 更精细的控制。考虑一下 nuitka --help 的输出。在编译中包括更少的模块,而使用正常的 Python 进行编译,会使编译速度更快。

如果你有一个带有动态加载文件的源目录,即通过 PYTHONPATH 正常导入语句后无法找到的目录(这将是推荐的方式),你总是可以要求一个特定的目录也应包括在可执行文件中:

python -m nuitka --follow-imports --include-plugin-directory=plugin_dir program.py

备注

如果你不做任何动态导入,只需在编译时设置你的 PYTHONPATH 就可以了。

只有在你进行 Nuitka 无法预测的 __import__() 调用时,才使用 --include-plugin-directory,因为它们依赖于命令行参数。Nuitka 也会警告这些,并指出该选项。

备注

产生的文件名在 Windows 上将是 program.exe,在其他平台上是 program.bin

备注

产生的二进制文件仍然依赖于 CPython 和使用的 C 扩展模块被安装。

如果你想能够把它复制到另一台机器上,使用 --standalone 并复制创建的 program.dist 目录并执行放在里面的 program.exe (Windows)或 program (其他平台)”

用例2 – 扩展模块的编译

如果你想编译一个单一的扩展模块,你所要做的就是这样:

python -m nuitka --module some_module.py

产生的文件 some_module.so 就可以代替 some_module.py 使用。

备注

这是留给读者的一个练习,以找出如果两者都存在会发生什么。

备注

选项 --follow-imports 和其他变体也可以工作,但所包含的模块只有在你导入了 some_module 的名字之后才会变得可以导入。

备注

产生的扩展模块只能加载到同一版本的 CPython 中,并且不包括其他扩展模块。

用例3–软件包的编译

如果你需要编译整个软件包并嵌入所有模块,那也是可行的,像这样使用 Nuitka:

python -m nuitka --module some_package --include-package=some_package

备注

包内容的包含需要手动提供,否则,包是空的。如果你愿意,你可以更具体一些,只包括一部分。位于包内的数据文件不会被这个过程嵌入,你需要用这种方法自己复制它们。

用例4–程序分发

对于分发到其他系统,有一种独立模式,它产生一个文件夹,你可以指定 --standalone

python -m nuitka --standalone program.py

在这种模式下,”跟随所有 import” 是默认的。你可以有选择地排除模块,特别是说 --nofollow-import-to,但是当在程序运行时试图导入它时,会出现 ImportError

对于要包含的数据文件,使用选项 --include-data-file=<source>=<target>,其中 source 是一个文件系统路径,但 target 必须指定为相对路径。对于单机版,你也可以手动复制它们,但这可能会做额外的检查,而对于单文件模式,不可能有手动复制。

要复制一个目录中的部分或全部文件,使用选项 --include-data-file=/etc/*.txt=etc/,你可以为这些文件指定 shell 模式,以及用尾部斜线表示的放置它们的子目录。

要复制整个文件夹的所有文件,你可以使用 --include-data-dir=/path/to/images=images,这将复制所有文件,包括潜在的子目录结构。你不能在这里进行过滤,也就是说,如果你只想要一个部分的拷贝,请事先删除文件。

对于包的数据,有一个更好的方法,使用 --include-package-data,它可以自动检测包的数据文件并将其复制过来。 它甚至可以接受 shell 风格的模式。

对于数据文件,你在很大程度上是靠自己的。Nuitka 记录了流行软件包所需要的数据,但它可能并不完整。如果你在这些方面遇到了问题,请提出来。

当这个工作完成后,如果你愿意,你可以使用 onefile 模式。

python -m nuitka --onefile program.py

这将创建一个单一的二进制文件,在 Linux 上,它甚至不会自己解压,而是将其内容作为一个文件系统循环回装,并使用该文件。

# Create a binary that unpacks into a temporary folder
python -m nuitka --onefile program.py

备注

还有更多的平台特定选项,例如与图标、闪屏和版本信息有关的选项,请考虑 --help 输出以了解这些细节,并查看 “Good Looks” 部分。

同样,在 Windows 上,对于临时文件目录,默认使用用户的目录,然而这可以用 --windows-onefile-tempdir-spec=%TEMP%\\onefile_%PID%_%TIME% 中给出的路径规范来覆盖,这是默认的,断言创建的临时目录不能发生冲突。

目前,这些扩大的 token 可以使用。

Token

这将扩展到什么?

示例

%TEMP%

用户临时文件目录

C:Users...AppDataLocalsTemp

%PID%

Process ID

2772

%TIME%

自纪元以来的时间,以秒为单位。

1299852985

%PROGRAM%

可执行文件的完整程序文件名。

C:SomeWhereYourOnefile.exe

备注

你有责任使提供的路径是唯一的,在 Windows 上,一个正在运行的程序将被锁定,虽然使用一个固定的文件夹名称是可能的,但在这种情况下,它可能导致锁定问题,即程序被重新启动。

通常你需要使用 %TIME% 或者至少使用 %PID% 来使路径唯一,这主要是针对使用情况的,例如你希望事物驻留在你选择的地方或者遵守你的命名惯例。

用例5 - Setuptools 轮子

如果你有一个 setup.pysetup.cfgpyproject.toml 驱动的为你的软件创建轮子的地方,把 Nuitka 用起来是非常容易的。

让我们从最常见的 setuptools 方法开始,你可以–当然是安装了 Nuitka,简单地执行目标 bdist_nuitka 而不是 bdist_wheel。它接受所有的选项,并允许你指定一些特定于 Nuitka 的内容。

# For setup.py if not you't use other build systems:
setup(
   ...,
   command_options={
      'nuitka': {
         # boolean option, e.g. if you cared for C commands
         '--show-scons': True,
         # options without value, e.g. enforce using Clang
         '--clang': ("setup.py", None),
         # options with single values, e.g. enable a plugin of Nuitka
         '--enable-plugin': 'anti-bloat',
         # options with several values, e.g. avoiding including modules
         '--nofollow-import-to' : ["*.tests", "*.distutils"],
      }
   },
)

# For setup.py with other build systems:
# The tuple nature of the arguments is required by the dark nature of
# "setuptools" and plugins to it, that insist on full compatibility,
# e.g. "setuptools_rust"

setup(
   ...,
   command_options={
      'nuitka': {
         # boolean option, e.g. if you cared for C commands
         '--show-scons': ("setup.py", True),
         # options without value, e.g. enforce using Clang
         '--clang': ("setup.py", None),
         # options with single values, e.g. enable a plugin of Nuitka
         '--enable-plugin': ("setup.py", 'anti-bloat'),
         # options with several values, e.g. avoiding including modules
         '--nofollow-import-to' : ("setup.py", ["*.tests", "*.distutils"]),
      }
   },
)

如果由于某种原因,你不能或不愿意改变目标,你可以把这个添加到你的 setup.py 中。

# For setup.py
setup(
   ...,
   build_with_nuitka=True
)

备注

为了暂时禁止编译,你可以删除上面这一行,或者把它的值编辑成 False,或者从环境变量中取值,如果你选择的话,例如 bool(os.environ.get("USE_NUITKA", "True"))。这由你决定。

或者你可以把它放在你的 setup.cfg 里。

[metadata]
build_with_nuitka = True

最后,但不是最不重要的,Nuitka也支持新的 build meta,所以当你已经有一个 pyproject.toml 时,简单替换或添加这个值:

[build-system]
requires = ["setuptools>=42", "wheel", "nuitka"]
build-backend = "nuitka.distutils.Build"

调整

图标

为了好看,你可以指定图标。在 Windows 上,你可以提供一个图标文件、一个可执行模板或一个 PNG 文件。所有这些都可以使用,甚至可以组合使用:

# These create binaries with icons:
python -m nuitka --onefile --windows-icon-from-ico=your-icon.png program.py
python -m nuitka --onefile --windows-icon-from-ico=your-icon.ico program.py
python -m nuitka --onefile --windows-icon-template-exe=your-icon.ico program.py

闪屏

当程序启动缓慢时,飞溅的屏幕很有用。Onefile 启动本身并不慢,但你的程序可能很慢,而且你无法真正知道所用的电脑会有多快,所以拥有它们也许是个好主意。幸运的是,有了 Nuitka,它们很容易为 Windows 添加。

对于闪屏,你需要将其指定为 PNG 文件,然后确保在你的程序准备好后禁用闪屏,例如已经完成导入,准备好窗口,连接到数据库,并希望闪屏消失。这里我们使用项目语法将代码与创建结合起来,编译时要注意:

# nuitka-project: --onefile
# nuitka-project: --onefile-windows-splash-screen-image={MAIN_DIRECTORY}/Splash-Screen.png

# Whatever this is obviously
print("Delaying startup by 10s...")
import time
time.sleep(10)

# Use this code to signal the splash screen removal.
if "NUITKA_ONEFILE_PARENT" in os.environ:
   splash_filename = os.path.join(
      tempfile.gettempdir(),
      "onefile_%d_splash_feedback.tmp" % int(os.environ["NUITKA_ONEFILE_PARENT"]),
   )

   if os.path.exists(splash_filename):
      os.unlink(splash_filename)

print("Done... splash should be gone.")
...

# Rest of your program goes here.

典型问题

内存问题和编译器错误

有时 C 语言编译器会崩溃,说他们不能分配内存,或者一些输入被截断了,或者类似的错误信息,显然是来自于此。这里有几个选择,你可以探索一下:

要求 Nuitka 使用更少的内存

有一个专门的选项 --low-memory,它影响了 Nuitka 的决策,使其在编译过程中避免大量使用内存,代价是增加编译时间。

避免 32 位 C 语言编译器/汇编器的内存限制

不要使用 32 位的编译器,而要使用 64 位的。如果你在 Windows 上使用 32 位的 Python,你最应该使用 MSVC 作为 C 编译器,而不是 MinGW64。MSVC 是一个交叉编译器,在该平台上可以比 gcc 使用更多的内存。如果你不在 Windows 上,当然就没有这个选择了。另外,使用 64 位的 Python 也能工作。

是否使用 LTO 编译

--lto=yes--lto=no,你可以将 C 语言的编译切换到只产生字节码,而不是直接产生汇编码和机器码,但在最后做整个程序的优化。这将大大改变内存的使用,如果你的错误来自汇编器,使用 LTO 将最能避免这种情况。

将 C 语言编译器切换为 clang

人们报告说,由于 gcc 的缺陷或内存占用,用 gcc 编译失败的程序在 Linux 上用 clang 可以正常工作。在 Windows 上,这可能仍然是一个选项,但需要先实现自动下载的 gcc,这将包含它。由于 MSVC 无论如何都是已知的更有效的内存,你应该去那里,如果你想使用 Clang,有支持 MSVC 中包含的那个。

在你的嵌入式 Linux 中添加一个更大的交换文件

在内存不足的系统上,你需要使用交换空间。用完了可能是一个原因,增加更多的交换空间,或者根本就没有,可能会解决这个问题,但是要注意,当编译器来回交换时,会使事情变得非常慢,所以要先考虑下一个提示,或者在它的基础上考虑。

限制编译工作的数量

使用 Nuitka 的 --jobs 选项,它不会同时启动许多 C 编译器实例,每个实例都在争夺稀缺的 RAM 资源。通过选择一个值,只有一个 C 编译器实例将被运行,在一个 8 核系统上,这将减少 8 倍的内存量,所以这是一个自然的选择。

动态 sys.path

如果你的脚本修改了 sys.path,例如插入与源代码相对的目录,Nuitka 目前将无法看到这些。然而,如果你将 PYTHONPATH 设置为结果值,你就可以编译它了。

单机版中缺少的数据文件

如果你的程序不能将数据归档,会导致各种不同的行为,例如,一个软件包可能会抱怨它的版本不对,因为 VERSION 文件检查默认为未知。没有图标文件或帮助文本,可能会引起奇怪的错误。

通常情况下,文件不存在的错误路径甚至是错误的,会暴露出编程错误,如未绑定局部变量。请仔细查看这些异常情况,牢记这可能是原因。如果你的程序没有独立运行,有可能是数据文件的原因。

单机中缺少 DLLs

Nuitka 有处理复制 DLLs 的插件。对于 NumPy、SciPy、Tkinter 等。

这些需要特殊处理,以便能够在其他系统上运行。手动复制它们是不够的,而且会产生奇怪的错误。有时较新版本的软件包,特别是 NumPy 可能不被支持。在这种情况下,你将不得不提出一个问题,并使用较旧的版本。

单机中的依赖性爬升

有些包是单一的 import,但对 Nuitka 来说,意味着要包括一千多个包(字面意思)。最典型的例子是 Pandas,它确实想插入和使用你能想象到的一切。多个框架用于语法高亮显示一切可以想象的东西,需要时间。

Nuitka 将来必须学习有效的缓存来处理这个问题。现在,你将不得不为这些处理巨大的编译时间。

现在,应该使用对抗依赖性蠕变的主要武器,即 anti-bloat 插件,它提供了有趣的能力,可以用来阻止不需要的导入,并在它们出现的地方给出一个错误。使用它,例如像这样 --enable-plugin=anti-bloat --noinclude-pytest-mode=nofollow --noinclude-setuptools-mode=nofollow 并查看其帮助输出。它可以为你选择的每一个模块,例如,也可以强迫 PyQt5 被视为独立模式的卸载。

一体化文件:寻找文件

对于 onefile ,主模块的 sys.argv[0]__file__ 之间有差异,那是由于使用引导到临时位置造成的。第一个将是原始的可执行路径,而第二个将是 bootstrap 可执行文件解包到的临时或永久路径。数据文件将在后一个位置,你的原始环境文件将在前一个位置。

给定两个文件,一个是你希望在你的可执行文件附近的,一个是你希望在 onefile 二进制文件内的,像这样访问它们。

# This will find a file near your onefile.exe
open(os.path.join(os.path.dirname(sys.argv[0]), "user-provided-file.txt"))
# This will find a file inside your onefile.exe
open(os.path.join(os.path.dirname(__file__), "user-provided-file.txt"))

没有控制台的 Windows 程序不会出现错误

为了调试的目的,移除 --windows-disable-console 或使用选项 --windows-force-stdout-spec--windows-force-stderr-spec,其路径与上述 --windows-onefile-tempdir-spec 所记载的相同。

小贴士

代码中的 Nuitka 选项

支持条件性选项,以及使用预定义变量的选项,这是一个例子:

# Compilation mode, support OS specific.
# nuitka-project-if: {OS} in ("Windows", "Linux", "Darwin", "FreeBSD"):
#    nuitka-project: --onefile
# nuitka-project-if: {OS} not in ("Windows", "Linux", "Darwin", "FreeBSD"):
#    nuitka-project: --standalone

# The PySide2 plugin covers qt-plugins
# nuitka-project: --enable-plugin=pyside2
# nuitka-project: --include-qt-plugins=sensible,qml

注释必须是一个行的开始,并且要使用缩进,以结束一个条件块,就像在 Python 中一样。目前除了上面演示的使用的关键字外,没有其他关键字。

你可以把任意的 Python 表达式放在那里,如果你想访问一个软件包的版本信息,你可以简单地使用 __import__("module_name").__version__,如果这将是需要的,例如启用或禁用某些 Nuitka 设置。Nuitka 所做的唯一一件事使得这不是 Python 表达式,就是为一组预先定义的变量扩展 {variable}

带有支持的变量的表格:

变量

这将扩展到什么?

示例

{OS}

使用的操作系统名称

Linux, Windows, Darwin, FreeBSD, OpenBSD

{Version}

Nuitka 版本

e.g. (0, 6, 16)

{Commercial}

Nuitka 商业版本

e.g. (0, 9, 4)

{Arch}

所用的架构

x86_64, arm64, etc.

{MAIN_DIRECTORY}

编译文件的目录

some_dir/maybe_relative

{Flavor}

Python 的变体

例如:Debian Python, Anaconda Python

Python 命令行旗标

对于向 Python 传递 -O-S 这样的东西,对于你的编译程序,有一个命令行选项名为 --python-flag= ,它使 Nuitka 模拟这些选项。

支持最重要的那些,当然还可以增加更多。

缓存编译结果

C 语言编译器,当用相同的输入文件调用时,将需要很长的时间和很多 CPU 来反复编译。确保你在使用 gcc 时安装和配置了 ccache (即使在 Windows 上)。它将使重复编译快得多,即使事情还不是很完美,即程序的改变会导致许多 C 文件的改变,需要重新编译而不是使用缓存的结果”

在 Windows 上,Nuitka 支持使用 ccache.exe,它将提供从官方来源下载,并自动进行。这是在 Windows 上使用它的推荐方式,因为其他版本可能会挂起。

如果在系统的 PATH 中找到 ccache ,Nuitka 就会接收它,也可以通过设置 NUITKA_CCACHE_BINARY 为二进制的完整路径来提供,这是为了在 CI 系统中使用。

对于 MSVC 编译器和 ClangCL 设置,使用 clcache 是自动的,包含在 Nuitka 中。

控制缓存的位置

各种缓存结果、下载、C 和 Nuitka 的缓存编译结果的存储,都是在一个与平台相关的目录中进行的,由 appdirs 包决定。然而,你可以通过设置环境变量 NUITKA_CACHE_DIR 来覆盖它的基本目录。这是为了在主目录不被持久化,但其他路径被持久化的环境中使用。

运行器

避免运行 nuitka 二进制文件,而使用 python -m nuitka 会 100% 确定你在使用你认为的东西。使用错误的 Python 会使它对好的代码出现 SyntaxError,对已安装的模块出现 ImportError。当你用 Python2 在 Python3 代码上运行 Nuitka 时,这种情况就会发生,反之亦然。通过明确地调用相同的 Python 解释器二进制文件,你可以完全避免这个问题。

最快的 C 语言编译器

事实证明,在 Windows 上使用 64 位 Python 的 pystone.exe 的最快二进制文件,比使用 MinGW64 明显更快,大概有 20% 的提升。所以推荐使用它,而不是 MSVC。使用 Clang7 的 clang-cl.exe 比 MSVC 快,但仍然明显比 MinGW64 慢,而且会更难使用,所以不推荐使用。

在 Linux 上,对于 pystone.bin,由 clang6 产生的二进制文件比 gcc-6.3 快,但差距不大。由于 gcc 更多的时候已经安装了,所以现在建议使用它。

C 语言编译时间的差异还没有被研究。

意外的速度减慢

使用Python DLL,就像标准的CPython那样,可能会导致意外的减速,例如在处理 Unicode 字符串的未编译的代码中。这是因为调用 DLL 而不是驻留在 DLL 中会导致开销,这甚至发生在 DLL 与本身,比一个 Python 全部包含在一个二进制文件中更慢。

所以如果可行的话,以静态链接为目标,目前只有 Anaconda Python 在非 Windows、Debian Python2、自编译的 Python(不要激活 --enable-shared,不需要),以及用 pyenv 创建的安装程序可以做到。

备注

在 Anaconda 上,你可能需要执行 conda install -c conda-forge libpython-static

独立的可执行文件和依赖性

传统上,为 Windows 制作独立可执行文件的过程涉及使用外部依赖性运行器,以便将必要的库与编译的可执行文件一起复制到分发文件夹。

有很多方法可以发现缺少什么。不要手动复制东西到文件夹中,特别是不要复制 DLL,因为那是行不通的。相反,要做错误报告,让 Nuitka 正确处理这些问题。

资源方面的 Windows 错误

在 Windows 上,Windows Defender 工具和 Windows 索引服务都会扫描刚创建的二进制文件,而 Nuitka 则想与之合作,例如添加更多资源,然后由于持有锁而随机阻止操作。请确保将你的编译阶段排除在这些服务之外。

Windows 独立程序的重新分配

无论是用 MingW 还是 MSVC 编译,独立程序都对 Visual C Runtime 库有外部依赖性。Nuitka 试图通过从你的系统中复制这些依赖的 DLL 来运送它们。

从微软 Windows 10 开始,微软提供的 ucrt.dll (通用 C 语言运行库)重新钩住了对 api-ms-crt-*.dll 的调用。

对于早期的 Windows 平台(和 wine/ReactOS),你应该考虑在执行 Nuitka 独立编译程序之前安装 Visual C Runtime 库。

根据所使用的C语言编译器,你需要以下的 redist 版本:

Visual C 版本

Redist Year

CPython

14.2

2019

3.5, 3.6, 3.7, 3.8, 3.9, 3.10

14.1

2017

3.5, 3.6, 3.7, 3.8

14.0

2015

3.5, 3.6, 3.7, 3.8

10.0

2010

3.3, 3.4

9.0

2008

2.6, 2.7

在使用 MingGW64 时,你需要以下的 redist 版本:

MingGW64 版本

Redist Year

CPython

8.1.0

2015

3.5, 3.6, 3.7, 3.8, 3.9, 3.10

一旦在目标系统上安装了相应的运行库,你可以从 Nuitka 编译的 dist 文件夹中删除所有 api-ms-crt-*.dll 文件。

在运行时检测 Nuitka

它不像其他工具那样设置 sys.frozen。对于 Nuitka,我们有模块属性 __compiled__ 来测试特定模块是否被编译。

性能

本章概述了目前对 Nuitka 性能的期望。这是一项正在进行中的工作,会随着我们的进展而更新。目前性能测量的重点是 Python 2.7,但 3.x 将在以后跟进。

pystone 结果

结果是这种输出的最高值,运行 pystone 1000 次,取最小值。这个想法是,最快的运行是最有意义的,并消除了使用高峰。

echo "Uncompiled Python2"
for i in {1..100}; do BENCH=1 python2 tests/benchmarks/pystone.py ; done | sort -n -r | head -n 1
python2 -m nuitka --lto=yes --pgo=yes tests/benchmarks/pystone.py
echo "Compiled Python2"
for i in {1..100}; do BENCH=1 ./pystone.bin ; done | sort -n -r | head -n 1

echo "Uncompiled Python3"
for i in {1..100}; do BENCH=1 python3 tests/benchmarks/pystone3.py ; done | sort -n -r | head -n 1
python3 -m nuitka --lto=yes --pgo=yes tests/benchmarks/pystone3.py
echo "Compiled Python3"
for i in {1..100}; do BENCH=1 ./pystone3.bin ; done | sort -n -r | head -n 1

Python

未编译

编译的 LTO

编译的 PGO

Debian Python 2.7

137497.87 (1.000)

460995.20 (3.353)

503681.91 (3.663)

Nuitka Python 2.7

144074.78 (1.048)

479271.51 (3.486)

511247.44 (3.718)

下一步该去哪里

记住,这个项目还没有完成。尽管 CPython 测试套件的工作近乎完美,但仍然需要更多的工作,特别是使它做更多的优化。试试吧。

在 Twitter 上关注我

Nuitka announcements and interesting stuff is pointed to on the Twitter account, but obviously with not too many details. @KayHayen.

报告问题或 bug

如果你遇到任何问题、错误或想法,请访问 Nuitka 错误跟踪器 并报告它们。

报告 bug 的最佳做法:

  • 请在你的报告中始终包括以下信息,用于底层的 Python 版本。你可以很容易地把它复制&粘贴到你的报告中。

    python -m nuitka --version
    
  • 尽量使你的例子最小化。也就是说,尽可能地删除那些对问题没有贡献的代码。最好是想出一个小的复制程序来说明这个问题,使用 print,当该程序运行时,会有不同的结果。

  • 如果问题是假性发生的(即不是每次都发生),尝试将环境变量 PYTHONHASHSEED 设为 0,禁用哈希随机化。如果这使问题消失,试着以 1 的步骤增加到一个哈希种子值,使它每次都发生,把它包括在你的报告中。

  • 不要在你的报告中包括创建的代码。考虑到适当的输入,它是多余的,如果没有改变 Python 或 Nuitka 源并重新运行的能力,我不太可能看它。

  • 不要发送文本的截图,那是不好的和懒惰的。相反,从控制台捕捉文本输出。

警示语

考虑慎重使用这个软件。尽管在发布前进行了许多测试,但事情还是有可能发生变化。我们非常欢迎你对 Nuitka 的反馈和补丁。

加入 Nuitka

我们非常欢迎你加入 Nuitka 的发展,并在所有的小事和大事上帮助完成这个项目。

Nuitka 的开发发生在 git 中。我们目前有以下 3 个分支:

  • main

    这个分支包含稳定版,只对其进行错误的热修复。它应该在任何时候都能工作,并受到支持。

  • develop

    这个分支包含正在进行的开发。它有时可能包含一些小的退步,但也有新的功能。在这个分支上,集成工作已经完成,而新功能可能在特性分支上开发。

  • factory

    这个分支包含未完成的和不完整的工作。它经常受到 git rebase 和公共暂存地的影响,我的开发分支的工作首先在这里进行。它只用于测试,建议在此基础上进行自己的开发。当更新它时,你经常会遇到合并冲突。只需通过做 git reset --hard origin/factory 来解决这些问题,然后切换到最新版本。

备注

开发者手册 解释了编码规则,使用的分支模型,与功能分支和热修复版本,Nuitka 设计等等。考虑阅读它以成为一个贡献者。这份文档是为 Nuitka 用户准备的。

奉献

Should you feel that you cannot help Nuitka directly, but still want to support, please consider making a donation and help this way.

不支持的功能

代码对象的 co_code 属性

对于本地编译的函数,代码对象是空的。Nuitka 的编译函数对象没有字节码,所以没有办法提供。

PDB

没有对编译后的函数进行跟踪,也没有对调试器进行跟踪。

优化

常量叠算

最重要的优化形式是常量叠算(Constant Folding)。这时,一个操作可以在编译时被完全预测。目前,Nuitka 为一些内建程序做了这些工作(但还不是全部,非常欢迎有人更仔细地研究这个问题!),它为二元/一元运算和比较等做了这些工作。

目前承认的常数:

5 + 6  # binary operations
not 7  # unary operations
5 < 6  # comparisons
range(3)  # built-ins

字面值是常量的一个明显来源,但也很可能是其他优化步骤,如常量传播或函数内联。所以这一点不应该被低估,也是成功优化的一个非常重要的步骤。每一个产生常量的选项都可能对生成的代码质量产生很大的影响”

状态

常量的折叠被认为已经实现,但它可能是不完整的,因为不是所有可能的情况都被抓住。当你在 Nuitka 中发现一个只有常量输入且没有折叠的操作时,请将其作为一个错误报告。

常量传播

在优化的核心,是试图在运行时确定变量的值和预测赋值的情况。它决定了它们的输入是否是常数或类似的值。一个表达式,例如一个模块变量访问,一个昂贵的操作,可能在整个函数范围的模块中是恒定的,那么就不需要或者不需要重复的模块变量查找。

考虑到例如模块属性 __name__,它很可能只被读取,所以它的值可以在编译时被预测为一个已知的常量字符串。然后,这可以作为常量折叠的输入。

if __name__ == "__main__":
    # Your test code might be here
    use_something_not_use_by_program()

状态

在模块属性中,目前只有 __name__ 被实际优化。同样可能的是,至少有 __doc__。在未来,随着 SSA 扩展到模块变量,这一点可能会得到改善。

内置名称查询

另外,如果内置的异常名称引用被用作模块级的只读变量,则会被优化:

try:
    something()
except ValueError:  # The ValueError is a slow global name lookup normally.
    pass

状态

这适用于所有的内置名称。当对这样的名字进行赋值时,或者它甚至是本地的,那么,当然就不做了。

内置回调预测

对于像 typelenrange 这样的内置调用,通常可以在编译时预测结果,特别是对于常数输入,结果值往往可以由 Nuitka 预先计算出来。它可以简单地确定结果或引发的异常,并用该值替换内置调用,允许更多的常量折叠或减少代码路径。

type("string")  # predictable result, builtin type str.
len([1, 2])  # predictable result
range(3, 9, 2)  # predictable result
range(3, 9, 0)  # predictable exception, range raises due to 0.

状态

内置的回调预测被认为已经实现。我们可以在编译时简单地模拟调用,并使用其结果或引发的异常。但我们可能还没有涵盖所有的内置程序。

有时,当一个内置的结果很大时,不应该预测它的结果。例如,调用 range() 可能会给出太大的值,无法将结果纳入二进制。那么就不做了。

range(100000)  # We do not want this one to be expanded

状态

这被认为是基本实现了。请为那些预先计算,但不应该由 Nuitka 在编译时用特定值计算的内置程序提出错误。

条件性声明的预测

对于条件性语句,有些分支可能永远不会被采纳,因为条件是可以预测的。在这些情况下,不采取的分支和条件检查被删除。

这通常可以预测这样的代码:

if __name__ == "__main__":
    # Your test code might be here
    use_something_not_use_by_program()

if False:
    # Your deactivated code might be here
    use_something_not_use_by_program()

它也将受益于不断的传播,或使其成为可能,因为一旦一些分支被删除,其他事情可能变得更可预测,所以这可以引发其他优化成为可能。

每删除一个分支都会使优化更有可能。随着一些代码分支的删除,访问模式可能更加友好。想象一下,例如,一个函数只在被删除的分支中被调用。有可能完全删除它,而这也可能产生其他后果。

状态

这被认为已经实现了,但为了获得最大的利益,需要在编译时确定更多的常数。

异常传播

对于在编译时确定的异常,有一个表达式会简单地引发异常。这些可以向上传播,收集潜在的 “副作用”,即在它发生之前已经执行的表达式的一部分,并且仍然要执行。

请考虑以下代码:

print(side_effect_having() + (1 / 0))
print(something_else())

The (1 / 0) can be predicted to raise a ZeroDivisionError exception, which will be propagated through the + operation. That part is just Constant Propagation as normal.

The call side_effect_having() will have to be retained though, but the print does not and can be turned into an explicit raise. The statement sequence can then be aborted and as such the something_else call needs no code generation or consideration anymore.

To that end, Nuitka works with a special node that raises an exception and is wrapped with a so-called “side_effects” expression, but yet can be used in the code as an expression having a value.

状态

The propagation of exceptions is mostly implemented but needs handling in every kind of operations, and not all of them might do it already. As work progresses or examples arise, the coverage will be extended. Feel free to generate bug reports with non-working examples.

Exception Scope Reduction

请考虑以下代码:

try:
    b = 8
    print(range(3, b, 0))
    print("Will not be executed")
except ValueError as e:
    print(e)

The try block is bigger than it needs to be. The statement b = 8 cannot cause a ValueError to be raised. As such it can be moved to outside the try without any risk.

b = 8
try:
    print(range(3, b, 0))
    print("Will not be executed")
except ValueError as e:
    print(e)

状态

This is considered done. For every kind of operation, we trace if it may raise an exception. We do however not track properly yet, what can do a ValueError and what cannot.

Exception Block Inlining

With the exception propagation, it then becomes possible to transform this code:

try:
    b = 8
    print(range(3, b, 0))
    print("Will not be executed!")
except ValueError as e:
    print(e)
try:
    raise ValueError("range() step argument must not be zero")
except ValueError as e:
    print(e)

Which then can be lowered in complexity by avoiding the raise and catch of the exception, making it:

e = ValueError("range() step argument must not be zero")
print(e)

状态

This is not implemented yet.

Empty Branch Removal

For loops and conditional statements that contain only code without effect, it should be possible to remove the whole construct:

for i in range(1000):
    pass

The loop could be removed, at maximum, it should be considered an assignment of variable i to 999 and no more.

状态

This is not implemented yet, as it requires us to track iterators, and their side effects, as well as loop values, and exit conditions. Too much yet, but we will get there.

Another example:

if side_effect_free:
    pass

The condition check should be removed in this case, as its evaluation is not needed. It may be difficult to predict that side_effect_free has no side effects, but many times this might be possible.

状态

This is considered implemented. The conditional statement nature is removed if both branches are empty, only the condition is evaluated and checked for truth (in cases that could raise an exception).

Unpacking Prediction

When the length of the right-hand side of an assignment to a sequence can be predicted, the unpacking can be replaced with multiple assignments.

a, b, c = 1, side_effect_free(), 3
a = 1
b = side_effect_free()
c = 3

This is of course only really safe if the left-hand side cannot raise an exception while building the assignment targets.

We do this now, but only for constants, because we currently have no ability to predict if an expression can raise an exception or not.

状态

Not implemented yet. Will need us to see through the unpacking of what is an iteration over a tuple, we created ourselves. We are not there yet, but we will get there.

Built-in Type Inference

When a construct like in xrange() or in range() is used, it is possible to know what the iteration does and represent that so that iterator users can use that instead.

I consider that:

for i in xrange(1000):
    something(i)

could translate xrange(1000) into an object of a special class that does the integer looping more efficiently. In case i is only assigned from there, this could be a nice case for a dedicated class.

状态

Future work, not even started.

Quicker Function Calls

Functions are structured so that their parameter parsing and tp_call interface is separate from the actual function code. This way the call can be optimized away. One problem is that the evaluation order can differ.

def f(a, b, c):
    return a, b, c


f(c=get1(), b=get2(), a=get3())

This will have to evaluate first get1(), then get2() and only then get3() and then make the function call with these values.

Therefore it will be necessary to have a staging of the parameters before making the actual call, to avoid a re-ordering of the calls to get1(), get2(), and get3().

状态

Not even started. A re-formulation that avoids the dictionary to call the function, and instead uses temporary variables appears to be relatively straight forward once we do that kind of parameter analysis.

Lowering of iterated Container Types

In some cases, accesses to list constants can become tuple constants instead.

Consider that:

for x in [a, b, c]:
    something(x)

Can be optimized into this:

for x in (a, b, c):
    something(x)

This allows for simpler, faster code to be generated, and fewer checks needed, because e.g. the tuple is clearly immutable, whereas the list needs a check to assert that. This is also possible for sets.

状态

Implemented, even works for non-constants. Needs other optimization to become generally useful, and will itself help other optimization to become possible. This allows us to e.g. only treat iteration over tuples, and not care about sets.

In theory, something similar is also possible for dict. For the later, it will be non-trivial though to maintain the order of execution without temporary values introduced. The same thing is done for pure constants of these types, they change to tuple values when iterated.

更新手册

这份文件是用 REST 写的。这是一种 ASCII 格式,人类可以阅读,但很容易用于生成 PDF 或 HTML 文档。

你可以在以下网站找到当前版本:https://nuitka.net/doc/user-manual.html

当前 PDF 版本:https://nuitka.net/doc/README.pdf