使用nuitka打包python代码为exe可执行程序

文章目录

  • 前言
  • 一、nuitka是什么?
  • 二、nuitka打包流程
  • 我的python环境
  • 1.下载C编译器
  • 2.下载Nuitka
  • 3.使用nuitka简单打包python代码
  • 4.使用nuitka打包pyqt5项目
  • 5.打包过程遇到的坑
  • 6.移植过程遇到的坑

  • 前言

    在项目中,我负责开发一个用于图像处理的可视化图形界面。人生苦短,我用python。python首选的GUI框架当然是pyqt5啦!项目很快就完成了,现在需要将我写的程序打包成exe文件分发给客户。python写代码一时爽,打包交付却没有C++版的QT方便。现有的python打包方式主要是使用pyinstaller,但是在调研过程中,发现pyinstaller打包的程序一般都非常大,而且运行的速度很慢。然后我就注意到了一个较新的工具nuitka,据说打包速度快,打包完的程序也不大,刚好符合我现有的需求——这时我还没意识到我面临的问题。由于这是一个较新的工具,在打包过程中我遇到了很多坑,而且可参考的解决方法并不多,然后我就开始了自己的填坑之路……也学到了教训——新技术不要轻易尝试,除非你有解决问题的能力。


    一、nuitka是什么?

    网上关于nuitka介绍一开始是看下面几个帖子:
    Python打包exe(32/64位)-Nuitka再下一城
    Nuitka之乾坤大挪移-让天下的Python都可以打包
    nuitka使用参考
    看了几遍总觉得稀里糊涂,一篇文章能说清楚的内容分成了好几篇文章,还相互引用,好像什么也没说清楚,只是告诉你怎么做,我按照同样的方法总会出现各种各样的问题。最后想明白了,只有理解了内容才能因地制宜地解决问题,最好的使用一个工具的教程往往是官方文档,其次才是别人总结的博客。

    nuitka是一个用来将python代码打包为exe可执行文件,方便其在没有相关环境的windows系统上运行的工具(貌似也支持打包成linux系统下的可执行程序,没需求暂未尝试)。其原理为:将部分python代码(自己写的部分)转换成C代码,以提高运行的速度;import的第三方包不进行编译,在运行时,通过一个python3x.dll的动态链接库执行第三方包的python代码,通过这样的方式减少exe包的大小。

    二、nuitka打包流程

    我的python环境

    conda 4.7.12
    Python 3.6.13
    numpy 1.16.4
    pyqt5 5.15.4

    1.下载C编译器

    nuitka的原理就是将部分代码转换为C,然后进行编译,所以需要先下载C编译器。
    (1)下载MinGW64 8.1,目前为止还是这个版本最稳定。下载地址:https://sourceforge.net/projects/mingw-w64/files
    百度网盘下载 密码:8888

    (2)将文件3 MinGW64 8.1 解压到C盘,并添加环境变量


    (3)打开cmd命令,使用gcc.exe –version测试是否添加上。一个坑:之前如果安装过c编译器可能添加过gcc环境变量导致MinGW64 8.1的环境变量被覆盖,早期的gcc版本在编译代码中可能会出现bug。

    (4)其他两个文件在安装Nuitka时会用上

    2.下载Nuitka

    (1)pip install nuitka 或者 conda install nuitka
    python环境下载工具应该是很基本的内容,速度慢可以添加镜像源,这一部分不再赘述

    3.使用nuitka简单打包python代码

    (1)新建一个简单的python文件,测试运行没有出错
    (2)使用nuitka xxx.py命令进行打包。在打包过程中会有提示下载一个包到***\nuitka\***这样一个文件夹中,下载进度条可能不动或者很慢,就可以使用 ctrl + C终止进程,手动将百度云下载的文件1解压到提示的这个文件家中
    (3)重新使用nuitka xxx.py命令进行打包。还会提示下载另一个包,同样的方式将文件2解压放入
    (4)重新使用nuitka xxx.py命令进行打包,这次应该就没问题了

    4.使用nuitka打包pyqt5项目

    先介绍以下nuitka的打包命令:

    –mingw64 默认为已经安装的vs2017去编译,否则就按指定的比如mingw(官方建议)
    –standalone 独立环境,这是必须的(否则拷给别人无法使用)
    –windows-disable-console 没有CMD控制窗口
    –output-dir=out 生成exe到out文件夹下面去
    –show-progress 显示编译的进度,很直观
    –show-memory 显示内存的占用
    –include-qt-plugins=sensible,styles 打包后PyQt的样式就不会变了
    –plugin-enable=qt-plugins 需要加载的PyQt插件
    –plugin-enable=tk-inter 打包tkinter模块的刚需
    –plugin-enable=numpy 打包numpy,pandas,matplotlib模块的刚需
    –plugin-enable=torch 打包pytorch的刚需
    –plugin-enable=tensorflow 打包tensorflow的刚需
    –windows-icon-from-ico=你的.ico 软件的图标
    –windows-company-name=Windows下软件公司信息
    –windows-product-name=Windows下软件名称
    –windows-file-version=Windows下软件的信息
    –windows-product-version=Windows下软件的产品信息
    –windows-file-description=Windows下软件的作用描述
    –windows-uac-admin=Windows下用户可以使用管理员权限来安装
    –linux-onefile-icon=Linux下的图标位置
    –onefile 像pyinstaller一样打包成单个exe文件(2021年我会再出教程来解释)
    –include-package=复制比如numpy,PyQt5 这些带文件夹的叫包或者轮子
    –include-module=复制比如when.py 这些以.py结尾的叫模块
    –show-memory 显示内存
    –show-progress 显示编译过程
    –follow-imports 全部编译
    –nofollow-imports 不选,第三方包都不编译
    –follow-stdlib 仅选择标准库
    –follow-import-to=MODULE/PACKAGE 仅选择指定模块/包编译
    –nofollow-import-to=MODULE/PACKAGE 选择指定模块/包不进行编译

    命令比较多,根据需要进行选择。我的需求是,编译包含pyqt5的代码,需要console进行调试(代码中的print会显示在console中),我的项目结构为:

    - package
    	- file1.py
    	- file2.py
    
    - utils
    	- file3.py
    
    - start.py
    

    打包思路:
    start.py作为主窗口的启动器,只引入pyqt5这一个第三方库。对start.py进行编译,package和utils两个包以及第三方包都不编译,后期直接将它们作为依赖放在exe可执行文件同一个文件夹下。

    缺点:package和utils两个文件没有编译也没有加密,以源代码的方式直接使用python解释器执行(其实比较蠢,相当于直接把源代码给别人了,但是我的代码里貌似没有需要加密的内容,单纯为了让我的代码可以在没有环境的windows电脑上运行)。当然为了保密将其也进行编译也可以,方法后面介绍。

    优点:可以避免几乎所有打包的坑。同时,方便改代码,打包好的入口程序可以直接调用我的python代码,我的python代码如果有修改,直接覆盖原来的.py即可,不用再编译。

    我使用的命令为:

    nuitka --standalone --mingw64 --show-progress --show-memory --nofollow-imports --plugin-enable=qt-plugins --include-qt-plugins=sensible,styles  --output-dir=out --windows-icon-from-ico=favicon.ico 软件的图标 start.py
    // --standalone环境独立
    // --mingw64选择之前下载的C编译器
    // --show-progress --show-memory显示进度和内存
    // --nofollow-imports所有包都不编译
    // --plugin-enable=qt-plugins --include-qt-plugins=sensible,styles 添加qt插件,导入相关包
    //  --output-dir=out --windows-icon-from-ico=favicon.ico 导出路径以及图标
    

    打包完成后会生成一个文件夹,包含xx.build和xx.dist两个目录,前一个无用。后一个就是我们需要的打包好的文件夹,里面有一个exe可执行文件。

    调试添加包的过程:
    (1)在xx.dist目录下打开cmd或者powershell(shift+鼠标右键,点击打开powershell)
    (2)运行./xx.exe
    (3)查看报错信息,缺少的第三方包就从D:\Anaconda3\envs\QT5(虚拟环境路径)下搜索关键字,将其复制保存到xx.dist目录下。缺少的自己的包,也将其复制过来。一步一步调试,直到把所有的依赖包全都复制到这个目录下,程序完美执行。(记得把这些依赖包复制备份)
    (4)之前的命令打包的程序为了调试有一个console黑框框,正式打包的话不要console,在之前的命令中添加--windows-disable-console,具体命令为:

    nuitka --standalone --mingw64 --show-progress --windows-disable-console --show-memory --nofollow-imports --plugin-enable=qt-plugins --include-qt-plugins=sensible,styles  --output-dir=out --windows-icon-from-ico=favicon.ico 软件的图标 start.py
    

    (5)重新打包好的程序,将之前备份的依赖包复制到相同位置,运行./xx.exe,成功运行!
    (6)如果后续需要程序方便分发给用户,可以使用Inno Setup编辑器将exe封装成安装包的方式,使用方法参考怎么把exe程序制作成安装包,傻瓜式操作即可。

    关于自己代码必须加密编译的情况(本人没有尝试,后续有需求再说):
    可以自己尝试使用:

    nuitka --mingw64 --module --show-progress --output-dir=o peewee.py
    // --module是将需要加密部分代码按照模块进行编译
    // 会生成一个.pyd文件,这部分代码可以放到Python3x\Lib\site-packages\目录下,测试程序是否完美运行,再尝试打包整个exe,它会把这个pyd一块儿打进exe。
    // 也可以放在最终打包的exe同目录下,通过python3x.dll调用
    

    5.打包过程遇到的坑

  • 首先nuitka对第三方包的导入很坑,除了--plugin-enable=qt-plugins --include-qt-plugins=sensible,styles对pyqt5相关包的导入编译没大的问题,对numpy,cv2,scipy等库编译都出现过问题,尤其是numpy(当然pandas,torch等库我的代码里没有涉及)。所以我直接--nofollow-imports不包含所有包,包括自己的包。
  • 那些教程里会提到使用--follow-import-to=need把自己写的包一同打入exe,但是他们都没说清楚,使用这个方式必须得need包里的代码没有import numpy等第三方库,否则也会把第三方包打进去!
  • 和上一个情况类似,start.py作为启动的程序,代码里也不要包含太多第三方库(cv2和numpy不要同时有,原因我猜cv2有部分依赖numpy的代码,打包后会生成numpy文件夹,导致自己导入numpy文件后会找不到numpy模块有冲突的情况,具体错误代码ModuleNotFoundError: No module named 'numpy._globals')。所以我干脆start.py启动器只导入PyQt5包。
  • 路径问题,代码中出现的相对路径在打包后会出现找不到资源文件的情况,可以使用下面的方式:
  • import os
    BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # 得到当前工作路径
    LAST_DIR = os.path.abspath(os.path.dirname(BASE_DIR)) # 当前上层路径
    
    # 调用资源文时,使用这种方式
    filepath = os.path.join(LAST_DIR,"icon.png")
    

    6.移植过程遇到的坑

    关于打包好的exe可执行文件在自己电脑上可以完美运行,但是移植到其他电脑上却出现问题。建议移植的时候还是使用带console的测试版本,方便打印错误日志。
    我遇到的相关的错误:
    错误代码:

    Traceback (most recent call last):
      File "G:\调试\numpy\core\__init__.py", line 17, in <module>
        from . import multiarray
      File "G:\调试\numpy\core\multiarray.py", line 14, in <module>
        from . import overrides
      File "G:\调试\numpy\core\overrides.py", line 7, in <module>
        from numpy.core._multiarray_umath import (
    ImportError: DLL load failed: 找不到指定的模块。
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "G:\调试\start.py", line 13, in <module>
      File "G:\调试\ImageProcessing\MainWiget.py", line 20, in <module>
        from utils.myUtils import MyUtils
      File "G:\调试\utils\myUtils.py", line 4, in <module>
        import numpy as np
      File "G:\调试\numpy\__init__.py", line 142, in <module>
        from . import core
      File "G:\调试\numpy\core\__init__.py", line 47, in <module>
        raise ImportError(msg)
    ImportError:
    
    IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!
    
    Importing the numpy c-extensions failed.
    - Try uninstalling and reinstalling numpy.
    - If you have already done that, then:
      1. Check that you expected to use Python3.6 from "G:\调试\python.exe",
         and that you have no directories in your PATH or PYTHONPATH that can
         interfere with the Python and numpy version "1.17.3" you're trying to use.
      2. If (1) looks fine, you can open a new issue at
         https://github.com/numpy/numpy/issues.  Please include details on:
         - how you installed Python
         - how you installed numpy
         - your operating system
         - whether or not you have multiple versions of Python installed
         - if you built from source, your compiler versions and ideally a build log
    
    - If you're working with a numpy git repository, try `git clean -xdf`
      (removes all files not under version control) and rebuild numpy.
    
    Note: this error has many possible causes, so please don't comment on
    an existing issue about this - open a new one instead.
    
    Original error was: DLL load failed: 找不到指定的模块。
    

    这个一开始我以为是numpy版本的问题,我是用conda numpy list命令发现有两个numpy——numpy和’numpy-base’于是全卸载了,重装了1.16.4版本,错误代码也变了:

    Traceback (most recent call last):
      File "G:\start.dist\start.py", line 13, in <module>
      File "G:\start.dist\ImageProcessing\MainWiget.py", line 20, in <module>
        from utils.myUtils import MyUtils
      File "G:\start.dist\utils\myUtils.py", line 4, in <module>
        import numpy as np
      File "G:\start.dist\numpy\__init__.py", line 140, in <module>
        from . import _distributor_init
      File "G:\start.dist\numpy\_distributor_init.py", line 34, in <module>
        from . import _mklinit
    ImportError: DLL load failed: 找不到指定的模块。
    

    综上,其实最根本的错误还是ImportError: DLL load failed: 找不到指定的模块,跟版本没有关系。最后想到移植程序跟环境路径肯定有很大关系。添加环境路径,表示不论在那个路径下都可以调用这个路径下的文件,会不会是在我的电脑上有环境变量代码调用了神秘路径上的文件,而其他人电脑上没有。于是在系统环境路径下各种尝试,发现删除某一个环境变量时,exe程序在我的电脑上也出现了相同的错误代码。
    破案了,就是这个路径里的文件!原来是安装annaconda自动为我添加的环境变量。

    解决方法: 将红框路径下的dll文件全部复制到打包好的xxx.exe路径中,移植没有问题了!当然,这里面依赖的dll文件具体是哪几个还需要试一试才知道,如果不嫌弃包太大了就全部复制。根据我的经验,numpy是需要nkl_开头的和libiomp5md.dll这些动态链接库。

    来源:ha_lee

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用nuitka打包python代码为exe可执行程序

    发表评论