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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

文章目录


前言

在项目中我负责开发一个用于图像处理的可视化图形界面。人生苦短我用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

1pip 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或者powershellshift+鼠标右键点击打开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相关包的导入编译没大的问题对numpycv2scipy等库编译都出现过问题尤其是numpy当然pandastorch等库我的代码里没有涉及。所以我直接--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这些动态链接库。
在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: python