使用 Nuitka 打包 Python 脚本为独立的可执行文件

本文记录在虚拟环境下使用Nuitka将Python 脚本或程序打包独立的可执行文件(exe)的过程,并给出一些打包建议。

主要内容包括:

(1)py文件打包的背景和现状;

(2)如何使用 Nuitka 打包 Python 脚本(加密和不加密);

(3)如何分离核心代码与依赖,优化打包流程;

(4)打包顽固派,如numpy,pandas,Scipy等,该如何处理?

图片

随着 Python 在软件开发和自动化任务中的广泛应用,如何方便地分发 Python 程序成为了许多开发者关注的问题。打包为独立的可执行文件(exe)能够让非技术用户在目标环境中无需安装 Python 或配置依赖,即可直接运行程序。这不仅提升了用户体验,还减少了环境配置带来的潜在问题。

一、为什么要打包为可执行文件?

为什么要打包为可执行文件?

  1. 提升可移植性
    将 Python 脚本打包为可执行文件后,可以在没有 Python 解释器的环境中直接运行,降低了对运行环境的依赖。

  2. 保护源代码
    可执行文件会将代码编译为二进制形式,避免源代码直接暴露,提升了代码的安全性。

  3. 简化分发
    用户无需额外安装库或解释器,仅需双击可执行文件即可使用,减少了运行前的环境配置步骤。

  4. 性能优化
    部分打包工具(如 Nuitka)会将 Python 脚本编译为 C/C++ 代码后再打包,从而带来一定的性能提升。

二、常用打包技术路线及其优缺点?

常用打包技术路线及其优缺点:

工具

优点

缺点

PyInstaller

支持多种平台,操作简单,支持大多数常见模块。

打包后的文件较大,运行效率未显著优化。

cx_Freeze

可自定义打包,支持跨平台应用开发。

配置稍显复杂,兼容性不如 PyInstaller。

Nuitka

将 Python 转换为 C/C++ 代码编译后打包,运行效率更高。

编译速度较慢,依赖 C/C++ 编译器,初始学习成本稍高。

三、打包常碰到的问题?

然而,当我们在打包一些较大的项目时,常碰到的问题一般包括:

✔ 直接打包失败;

✔ 打包的文件过大;

✔ 打包时间过长;

✔ 打包结果无法运行,报错无法导入各种模块和依赖等。

尤其是当代码中包含一些模块,如numpy、pandas、pytorch、cv2等;

四、如何使用 Nuitka 打包 Python 脚本?

本文主要分享在虚拟环境下(python版本3.9),使用Nuitka(2.5.6) 打包python脚本的过程。

使用 Miniconda 创建虚拟环境并结合 Nuitka 打包的优势:

  1. 虚拟环境隔离
    Miniconda 提供的虚拟环境可以隔离项目依赖,确保打包时只包含必要的依赖,避免不必要的体积增加。

  2. 精简环境
    Miniconda 的轻量特性使得构建的环境更加精简,降低了冗余文件对最终打包结果的影响。

  3. 与 Nuitka 高度兼容
    Nuitka 与 Miniconda 配合使用,可以轻松配置所需的编译器(如 MinGW),提供更高效的 C/C++ 编译支持,优化可执行文件的运行性能。

在后续内容中,我们将详细介绍如何使用 Nuitka 从零开始打包 Python 脚本或程序,包括:

  1. 配置 Miniconda 虚拟环境,安装必要的依赖。

  2. 准备打包脚本及相关文件。

  3. Nuitka 的基本用法和高级配置。

  4. 提供优化建议和常见问题的解决方法。

通过这些步骤,您可以快速将 Python 脚本打包为高效、可靠的可执行文件,为程序分发和部署提供便利。

4.1 安装miniconda

下载地址:https://repo.anaconda.com/miniconda/

安装过程一般不会有什么问题,略过。

4.2 创建虚拟环境

①在cmd窗口中输入以下命令;

conda create –prefix C:\ProgramData\Miniconda3\envs\package_exe_test python=3.9.21

图片

②输入:y,回车后等到虚拟环境创建;

③创建完成后,按提示激活虚拟环境 package_exe_test ;

图片

④添加镜像;

  • 镜像源

  • 大多数国内镜像源都会同时托管 defaults 和 conda-forge 通道。这意味着你可以通过这些镜像源访问 both Anaconda 官方提供的包和 conda-forge 社区提供的包。
  • conda-forge 是一个独立的通道,不仅仅存在于某些特定的镜像源中。你也可以直接使用 conda-forge 通道而不需要通过国内镜像源。
  • 选择策略

  • 如果你希望通过国内镜像源加速包的下载,可以选择一个可靠的国内镜像源,并确保它包含了 conda-forge 通道。

  • 如果你需要特定的包并且不确定是否在某个镜像源中可用,可以直接添加 conda-forge 通道作为备选。

  • 添加镜像命令示例:

    conda config –add channels defaults conda config –add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config –add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/ conda config –set show_channel_urls yes

    移除镜像命令示例:

    conda config –remove-key channels

    验证镜像配置:

    conda config –show channels

    设置通道优先级(可选):

  • 默认情况下,conda 会按照通道的顺序查找包。你可以通过设置通道优先级来确保 conda-forge 中的包优先被考虑。

  • 使用以下命令将 conda-forge 设置为最高优先级

    conda config –set channel_priority strict

  • 图片

    4.3 安装python脚本或程序中所需要的包或模块

  • 将python脚本或程序所需的包或模块的信息,导出到requirements.txt中进行安装

    conda install --file path/to/requirements.txt

  • 逐一手动安装,如:

    conda install openpyxl

  • 最关键的是,必须安装nuitka,直接使用命令:conda install nuitka -c conda-forge安装,无报错(有些文章会提到无法正常安装nuitka的解决办法)。

    图片

    4.4 Nuitka打包命令参数说明

    要合理有效的根据打包需求使用Nuitka打包脚本或程序,需要对常用的Nuitka选项进行了解。

    可以使用:nuitka –help查看Nuitka打包选项详情。

    图片

    参数很多,以下是 Nuitka 的常用选项,分为控制插件、模块导入和文件包含等方面。

    选项 说明
    --standalone 打包成独立的可执行文件,包含所有依赖项(不需要额外的 Python 解释器)。
    --onefile 将所有文件(包括依赖项)打包到一个单独的 .exe 文件中。
    --mingw64 使用 MinGW64 编译器进行打包,通常比默认的 MSVC 编译更高效,适合 Windows 环境。
    --enable-plugins=PLUGIN_NAME 启用指定插件(如 tk-inter, numpy, pandas 等),以增强功能或解决依赖问题。可以用 –plugin-list 查看所有支持的插件。
    --windows-disable-console 禁用控制台窗口(适用于 GUI 应用程序)。
    --windows-icon-from-ico=<file> 设置应用程序的图标,需提供 .ico 格式的文件路径。
    --output-dir=<directory> 指定生成的文件(如 .exe 和其他依赖项)的输出目录。
    --company-name=<name> 设置应用程序的公司名称。
    --product-name=<name> 设置产品名称。
    --file-version=<version> 设置文件版本号,例如 1.0.0.0
    --product-version=<version> 设置产品版本号,例如 1.0.0.0
    --file-description=<description> 添加文件描述信息,例如 "独立打包的应用程序"。
    --show-progress 显示打包过程中的详细进度信息。
    --show-memory 显示打包过程中内存使用情况,便于性能调试。
    --remove-output 清理生成的中间文件和临时文件夹,仅保留最终的可执行文件。
    --include-module=MODULE 包含单个模块,通过 Python 命名空间指定。
    --include-package=PACKAGE 包含整个包(及其子包),通过 Python 命名空间指定。
    --follow-import-to=MODULE/PACKAGE 指定递归处理的模块或包(及其子包),可以多次提供。例如:–follow-import-to=numpy,matplotlib。
    --nofollow-import-to=MODULE/PACKAGE 指定不递归处理的模块或包,支持通配符(如 *.tests)。优先级高于其他包含选项。
    --python-version=<version> 指定 Python 版本,例如 3.9(需与当前环境的 Python 版本匹配)。
    --follow-imports 默认选项。递归处理所有导入模块。适用于 standalone 模式 或需要完整依赖的场景。
    --nofollow-imports 禁止递归处理任何导入模块,适合不需要包含外部模块的场景,但通常不适用于 standalone 模式。

    如上表,高亮的命令参数是Nuitka 中关于控制模块导入行为的选项,这些参数也是灵活打包的一个关键点。

    选项 行为描述 应用场景 示例
    --enable-plugins=PLUGIN_NAME 启用指定的插件。可以启用多个插件,每个插件需要使用其名称。通过 --plugin-list 查看所有可用插件的列表。 启用对特定模块优化或扩展功能的插件。例如,启用支持 Pandas、Numpy 等库的插件以优化编译结果。 nuitka --standalone --enable-plugins=pandas,numpy my_script.py
    --include-module=MODULE 显式包含单个模块,无论它是否在代码中被直接或间接导入。指定模块名(Python 命名空间,例如 some_package.some_module)。 某些动态加载的模块或运行时未明确显示导入的模块需要被显式包含,例如使用 importlib 导入的模块。 nuitka --standalone --include-module=custom_module my_script.py
    --include-package=PACKAGE 显式包含整个包及其子模块,无论它们是否在代码中被导入。指定包名(Python 命名空间,例如 some_package.sub_package)。 某些包使用动态加载模块的方式运行,或者运行时依赖的子模块未直接导入,需要显式包含整个包。例如,打包包含插件机制的软件时显式包含其插件目录。 nuitka --standalone --include-package=my_package my_script.py
    --follow-import-to=MODULE/PACKAGE 对指定的模块或包及其子模块递归追踪导入。可以通过指定模块或包的名称来定义。 用于控制追踪范围,只追踪某些重要模块或包,而避免对所有导入的模块进行追踪,从而减少编译时间和最终文件大小。例如,只追踪应用程序核心逻辑涉及的模块。 nuitka --follow-import-to=my_core_module my_script.py
    --nofollow-import-to=MODULE/PACKAGE 明确不追踪指定的模块或包,即使它们被代码显式或间接导入。支持使用通配符模式(如 *.tests)。 某些模块或包(如测试模块)对程序运行不必要,或会增加编译时间和文件大小,需要显式排除。例如,排除不相关的测试模块或示例模块。 nuitka --standalone --nofollow-import-to=*.tests my_script.py
    --follow-imports 递归追踪代码中导入的所有模块。 默认情况下,独立模式会启用该选项,用于确保所有导入模块都被正确追踪和包含。适用于需要打包完整应用程序的场景,但会增加编译时间和文件大小。 nuitka --standalone --follow-imports my_script.py
    --nofollow-imports 不递归追踪任何导入模块,仅包含代码中显式定义的模块。 适用于需要最小化文件大小,且明确知道哪些模块必须被包含的场景。例如,只需要打包单个脚本且无外部依赖时使用。 nuitka --nofollow-imports my_script.py

    示例解析:

    1. 启用插件

    nuitka –standalone –enable-plugins=tk-inter my_script.py

    行为:启用 tk-inter插件,用于创建图形用户界面(GUI)。

    2. 显式包含模块

    nuitka –standalone –include-module=custom_module my_script.py

    行为:强制包含 custom_module 模块,即使该模块未在代码中直接导入。适用于动态加载的模块。

    3. 显式包含包

    nuitka –standalone –include-package=my_package my_script.py

    行为:强制包含 my_package 及其所有子模块。适用于插件式架构的项目。

    4. 跟踪指定模块

    nuitka –follow-import-to=my_core_module my_script.py

    行为:仅跟踪 my_core_module 模块及其子模块的导入,忽略其他模块,减少文件大小和编译时间。

    5. 排除测试模块

    nuitka –standalone –nofollow-import-to=*.tests my_script.py

    行为:不跟踪任何 tests 子模块,即使它们被导入。适用于生产环境的优化打包。

    6. 默认跟踪所有导入

    nuitka –standalone –follow-imports my_script.py

    行为:跟踪所有代码中导入的模块,确保完整性。适用于需要打包完整程序的场景。

    7. 禁用所有跟踪

    nuitka –nofollow-imports my_script.py

    行为:仅包含脚本中显式导入的模块,减少二进制文件大小。适用于简单无依赖的脚本。

    4.5 使用Nuitka打包python脚本或程序

    来到这里,开始步入打包python脚本或程序为可执行文件的关键内容。

    对打包的结果,一般的要求是程序文件要小、启动响应要快、执行速度要快

    这些要求,本身就是一个伪命题。尤其是代码量较大,导入模块或包较多时,如果将所有文件打包到一个单独的exe文件中,程序文件就不可能小,启动速度也不可能快,而python语言本身,性能就不是它的优势。

    首先,应该根据实际需求和客观情况,合理的选择打包的方式:

    –打包成独立的可执行文件,包含所有依赖项;

    –打包成一个单独的 .exe 文件;

    其次,要考虑打包的效率,一般来说,几分钟内完成一次打包任务是可接受的;但几个小时甚至更长时间才能完成一次打包,其打包的技术路线是需要改进的。

    –是否考虑对python源代码进行加密。

    最重要的是,得能正常完成打包过程,打包程序能正常运行。

    以下是整理的打包需求表:

    项目 描述
    打包方式 文件夹 / 独立文件
    包含依赖项 是 / 否
    生成的文件类型 .exe / .app / Linux 可执行文件
    打包效率 几分钟内完成 / 几个小时甚至更长时间
    加密方式 加密 / 不加密
    调试模式 是 / 否

    根据一般的打包需求,本文给出四个示例进行说明

    ①将脚本打包为独立的可执行文件(或独立的exe文件),非加密

    ②将脚本打包为独立的可执行文件(或独立的exe文件),加密

    ③如何分离核心代码与依赖,优化打包流程;

    ④打包顽固派,如numpy,pandas,Scipy等,该如何处理?

    测试脚本“main_base.py”核心代码如下:

    import tkinter as tkfrom tkinter import ttk, messageboximport mathdef on_submit():    """获取用户输入并计算平方根"""    try:        # 获取用户输入        user_input = entry.get()        number = float(user_input)        if number < 0:            messagebox.showerror("错误", "请输入非负数!")        else:            # 计算平方根            result = math.sqrt(number)            messagebox.showinfo("输出", f"数字 {number} 的平方根是:{result:.2f}")    except ValueError:        messagebox.showwarning("警告", "请输入有效的数字!")def main(value=100):    """主函数,包含默认值"""    # 创建主窗口    root = tk.Tk()    root.title("平方根计算器")    # 设置窗口大小    root.geometry("300x200")    # 标签    label = ttk.Label(root, text=f"请输入一个数字 (默认值: {value}):")    label.pack(pady=10)    # 输入框    global entry    entry = ttk.Entry(root, width=30)    entry.insert(0, str(value))  # 默认填入 value    entry.pack(pady=5)    # 提交按钮    submit_button = ttk.Button(root, text="计算平方根", command=on_submit)    submit_button.pack(pady=10)    # 运行主循环    root.mainloop()if __name__ == "__main__":    main()

    其功能为在输入框中输入一个数值,对其开方后输出。

    程序运行过程如下图:

    图片

    4.5.1 将脚本打包为独立的可执行文件(或独立的exe件),不加密

    将脚本文件放到“D:\mypackage”,注意,所有的打包脚本或程序放到非中文路径下。

    图片

    (1)激活虚拟环境package_exe_test,并将目录切换至脚本所在的目录

    图片

    (2)输入命令:

    nuitka –standalone –mingw64 –enable-plugin=tk-inter –show-memory –show-progress –output-dir=h –windows-icon-from-ico="D:\A53.ico" –company-name="Xmy" –product-name="GIS探案工具箱" –file-version="1.0" –product-version="1.0" –file-description="免费工具箱" main_base.py

    图片

    以下是对命令中各选项的解释:

    命令选项 说明
    nuitka 调用 Nuitka 编译工具。
    --standalone 将 Python 脚本和所有依赖打包成独立的可执行文件,无需额外安装 Python 运行环境。
    --mingw64 指定使用 MinGW64 编译器进行编译,适用于 Windows 平台。
    --enable-plugin=tk-inter 启用 Tkinter 插件,支持 Tkinter 界面功能。
    --show-memory 显示打包过程中的内存使用信息,用于调试和优化内存占用。
    --show-progress 显示打包过程中的详细进度信息,便于实时跟踪打包状态。
    --output-dir=h 指定输出目录为 h,生成的文件和相关文件会保存在此目录下。
    --windows-icon-from-ico="D:\A53.ico" 设置生成的可执行文件的图标,使用指定路径的 .ico 文件。
    --company-name="Xmy" 设置公司名称为 Xmy,用于在文件属性中显示。
    --product-name="GIS探案工具箱" 设置产品名称为 GIS探案工具箱,用于在文件属性中显示。
    --file-version="1.0" 设置文件版本为 1.0,用于文件属性显示和版本管理。
    --product-version="1.0" 设置产品版本为 1.0,同样用于文件属性显示和版本管理。
    --file-description="免费工具箱" 设置文件描述为 免费工具箱,显示在文件属性的详细信息中。
    main_base.py 指定要打包的 Python 脚本文件,即 main_base.py

    总结:

    该命令用于将 interview_data_processor.py 打包成一个独立运行的 .exe 文件,包含所有依赖项,适配 Tkinter GUI 界面,同时带有自定义的图标、文件描述以及版本信息。打包输出的结果会保存在指定的 h 目录中。

    命令执行完成后,将在当前目录下生成文件夹h,存放打包后的结果。将生成两个文件夹,其中“interview_data_processor.dist”文件夹为打包过程文件夹,可直接删除,当然,若增加命令参数“–remove-output”后,将不输出此文件夹。

    图片

    脚本在正确执行后,打开的界面如下图:

    图片

    对于有控制台的程序,在平时的打包过程中,建议先不要禁用控制台窗口,即在命令行中不要添加“–windows-disable-console”参数,如此,在运行打包后的可执行文件,会在控制台中输出报错信息(若存在),可以根据报错信息进行相应的处理。可以称这个模式为“调试模式”,待程序能正确打包运行后,添加“–windows-disable-console”参数即可;

    针对当前脚本文件,若想生成独立的exe文件,不需要控制台窗口,且清除打包过程文件,命令如下:

    nuitka –standalone –onefile –mingw64 –windows-disable-console –enable-plugin=tk-inter –show-memory –show-progress –remove-output –output-dir=h –windows-icon-from-ico="D:\A53.ico" –company-name="Xmy" –product-name="GIS探案工具箱" –file-version="1.0" –product-version="1.0" –file-description="免费工具箱" main_base.py

    图片

    4.5.2 将脚本打包为独立的可执行文件(或独立的exe件),加密

    本文所说的“加密”,是指通过将py文件编译为pyd文件来保护源代码不被直接查看,.pyd 文件是 Windows 上的动态链接库(DLL),可以包含 C/C++ 编译后的代码,从而隐藏原始的 Python 源代码。

    本场景下的打包主要步骤:

    ①将py文件编译为pyd文件;

    ②将pyd文件拷贝至当前python虚拟环境的site-packages目录下(建议将pyd文件组成自定义包);

    ③打包pyd文件。

    主要步骤示例说明:

    ①将py文件编译为pyd文件命令如下:

    nuitka –mingw64 –module –output-dir=o main_base.py

    图片

    如果一个包,这个包又包含子包,那么如何将它们编译为pyd文件的包呢?以及pyd文件或包的二次加密呢?       这部分不在本文讲解。

    执行上述命令后,将在m文件夹下输出编译pyd文件的结果:

    图片

    ②将main_base.cp39-win_amd64.pyd文件拷贝至当前python虚拟环境的site-packages目录下,并将pyd文件组成自定义包;

    图片

    ③打包pyd文件

    在“D:\mypackage”目录下新建一个main.py脚本文件,动态导入pyd包。

    关键点:

    Nuitka 在 --standalone 模式下会尝试分析所有模块的依赖关系,但有时会遗漏一些动态导入的子模块。

    由于 Nuitka 打包时需要显式指定动态依赖,如果没有正确添加,可能会导致运行时找不到相关模块。

    解决办法

    当你的主程序(main.py)中存在导入pyd包的情况,请将pyd文件中所有导入的模块或包在主程序中再导入一遍。

    图片

    随后,再虚拟环境中输入命令:

    nuitka –standalone –mingw64 –enable-plugins=tk-inter –show-memory –remove-output –show-progress –output-dir=h –windows-icon-from-ico="D:\A53.ico" –company-name="Xmy" –product-name="GIS探案工具箱" –file-version="1.0" –product-version="1.0" –file-description="免费工具箱" main.py

    打包结果和程序执行过程如下图:

    图片

    同样的,若想生成独立的exe文件,不需要控制台窗口,且清除打包过程文件,命令如下:

    nuitka –standalone –onefile –mingw64 –windows-disable-console –enable-plugin=tk-inter –show-memory –show-progress –remove-output –output-dir=h –windows-icon-from-ico="D:\A53.ico" –company-name="Xmy" –product-name="GIS探案工具箱" –file-version="1.0" –product-version="1.0" –file-description="免费工具箱" main.py

    4.5.3 分离核心代码与依赖,优化打包流程

    核心代码持续迭代优化带来的不断打包,可能会带来一些挑战:

    ①程序的绝大部分依赖存在重复打包;

    ②程序中的某些依赖打包慢、且不容易打包成功;

    为了解决这个困难,我们可以采取以下思路进行打包:

    将核心代码部分,单独放置在一个文件夹(如:User_Customized)中,并且在打包时,利用选项” –nofollow-import-to=User_Customized”,将User_Customized文件夹内的依赖移除,打包完成后,采取手动配置缺失包或模块的方式解决运行环境问题。

    后续核心模块更新后,仅需复制核心模块到打包目录下替换即可。

    为了演示这个过程,新创建一个脚本“get_number.py”,定义一个函数,随机返回一个整数,传递给“main_base.py”中的main函数,将脚本放到文件夹User_Customized中。文件组织和代码内容如下图:

    图片

    运行以下打包命令:

    nuitka –standalone  –nofollow-import-to=User_Customized –enable-plugins=tk-inter –output-dir=p main_base.py

    打包结果和运行提示如下图:

    图片

    按控制台窗口提示的报错信息,手动补齐缺失的模块和包后,正确打开程序界面并执行程序内容。

    图片

    核心模块代码更新后,如何处理?

    如,将脚本“get_number.py”中的函数返回整数的逻辑改为返回小数,将该脚本复制替换打包结果“D:\mypackage\p\main_base.dist\User_Customized”下的get_number.py即可。

    图片

    4.5.4 打包顽固派,该如何处理?

    实际上,无论手动配置运行环境缺失的模块或包,还是在打包过程中,都会碰到一些比较难打包成功的包,如numpy、Scipy、pandas等。

    针对这些打包“顽固”的包,利用选项“–nofollow-import-to=numpy,pandas“,在打包的过程中先忽略这些包,后续在调试模式下,手动配置运行环境。

    由于nuitka 、numpy以及python的版本,在一些文章的介绍中存在一些依赖关系,下面给出本次示例的版本情况:

    图片

    为了增加numpy模块,将脚本“get_number.py”中的函数返回整数使用numpy实现。

    图片

    在老版本nuitka,对于numpy的打包,可以使用以下命令:

    nuitka –standalone –enable-plugin=tk-inter –enable-plugin=numpy  –output-dir=y main_base.py

    但新版本的nuitka,其插件已经不再支持包含numpy,使用nuitka –plugin-list可查看所有可用插件:

    图片

    再使用当前命令打包,碰到numpy依赖时,仅是警告提示,打包过程不会报错,能成功打包。

    图片

    打包文件也能正确运行:

    图片

    看似简单,为了能正确打包numpy,实际上小编尝试了很多博主的方法。

    这里有一个很关键的点:在虚机中安装numpy,不能使用conda命令去安装。可以使用pip命令直接安装,推荐从https://pypi.org/下载所需版本的numpy文件,安装后再打包。

    当前代码,依赖中较大的包只有numpy,打包的exe文件有15M,若再包含较多其他较大的包,exe文件将变的很大,在点集exe文件运行时,启动会较慢。

    针对这些较难打包成功的包,另一种推荐的做法是:利用选项“–nofollow-import-to=numpy,pandas“,在打包的过程中先忽略这些包,后续在调试模式下,手动配置运行环境;

    nuitka –standalone –enable-plugin=tk-inter –nofollow-import-to=numpy  –output-dir=n main_base.py

    打包完成后,exe文件仅3.7M,启动很快,但是会提示缺失numpy模块和其他依赖。

    图片

    缺失的模块和依赖,可以从现在的虚拟环境package_exe_test的lib根目录、site-packages根目录以及DLLs根目录下,按报错信息提示缺失的内容,拷贝至“……\main_base.dist”目录下即可。

    图片

    图片

    作者:craybb

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用 Nuitka 打包 Python 脚本为独立的可执行文件

    发表回复