农夫山泉,喝起来有点甜;python应用,运行起来有点慢。但,除了慢,它就别无缺点了。
前言
只有打包,才方便分发,才能流传后世。故此,颇有研究必要。
PyQt + pyinstaller成为一些中小项目界面包装和软件分发的优良选择。多快好省,省去很多烦恼。
打包主要是收集依赖,其次是形成正确的目录结构,最后才是包装成平台需要的可执行程序。
打包工具:PyInstaller,nuitka,cx_Freeze,PyOxidizer,PyInstaller-Qt,PyInstaller-PySide,Py2exe
首选pyinstaller,此选nuitka。pyinstaller打包速度快,但nuitka打包后的程序运行更快,代码更难被破解。nuitka打包会遇到各种问题。
Python程序分发思考
- pyinstaller 打包, – 需要各平台单独打包。有反编译风险。
- 用 Cython 将 py 编译成 .pyd/.so ->在用 pyinstaller 进行打包 。 – 各平台单独打包分发、反编风险小
- pyarmor 是一个专业混淆代码库,也是编译成字节码然后用-> pyinstaller 打包分发 ---- 各平台单独打包分发、反编风最小
- Nuitka 直接使用 Clang 进行编译运行。
pyc文件: Python源代码import后,编译生成的字节码,类似于java的class文件。例如__pycache__ 目录用于存放pyc
PYD 文件是用Python 编写的动态链接库,可以在运行时由其他Python 代码运行。 它包含一个或多个Python 模块,便于代码重用,并为编写应用程序提供模块架构。pyd库可以被python调用,也可以被C或其它程序调用。ctypes.cdll.LoadLibrary()用于加载pyd文件。
Cython
cythonize可以将py文件编译成pyd。
pip install Cython
cythonize -i -3 --directive always_allow_keywords=true xxx.py
python中调用pyd:
import ctypes
# 加载 Pyd 文件
mylib = ctypes.cdll.LoadLibrary('mylib.pyd')
# 调用 Pyd 文件中的函数
result = mylib.add(1, 2)
# 打印结果
print(result)
pyinstaller
pyinstaller options script.spec
当通过spec文件来生成app文件的时候只有下面几个参数是有用的:
- –upx-dir=
- –distpath=
- -noconfirm=
- –ascii
nuitka
Nuitka提供了一个激进的解决方案。它将Python程序编译为C语言二进制文件——不是通过将CPython运行时与程序字节码打包,而是通过将Python指令翻译成C语言。其结果能够以压缩包的形式分发,也可以与其他第三方产品一起打包到安装程序中。Nuitka还试图保持与Python生态系统的最大兼容性,因此NumPy等第三方库可以可靠地工作。Nuitka还尽可能地对编译后的Python程序进行性能改进,但同样不会牺牲整体兼容性。
ccache
ccache(“compiler cache”的缩写)是一个编译器缓存,该工具会高速缓存编译生成的信息,并在编译的特定部分使用高速缓存的信息, 比如头文件,这样就节省了通常使用 cpp 解析这些信息所需要的时间。如果某头文件中包含对其他头文件的引用,ccache会用那个文件的 cpp-parsed版本来取代include声明,不是真正去读取、理解并解释其内容,ccache 只是将最终的文本拷贝到文件中,使得它可以立即被编译。ccache是以空间换取速度,ccache非常适合经常make clean(或删除out目录)后重新编译的情况。
nuitka打包原理
nuitka打包过程,就是一个将引用的库打包成pyd的过程。根据py文件中from、import遍历相应模块文件打包到打包程序中,一旦遇到pyd,则不再递归查找下一层模块py文件。
nuitka主要参数
- –nofollow-imports, 所有的import全部不使用,交给python3x.dll/libpython3.11.dylib执行
- –follow-imports, 仅遵循通过import语句在代码中显式声明的导入。它不处理动态导入即importlib.import_module(‘modulename’)
- –follow-import-to=need , need为你需要编译成C/C++的py文件夹名称,可以指定整个package,也可以是package下的某个模块
- –include-package=mypackage : 导入某个包
- –include-module=mypackage.mymodule : 导入包中某个模块
注意:推荐使用python完整安装的版本,不要使用pyenv安装的,否则面临一堆的编译问题。
python -m nuitka \
--standalone \
--onefile\
--enable-plugin=pyqt5\
--macos-create-app-bundle\
--output-dir=build\
--assume-yes-for-download\
--macos-app-version=2.0\
--disable-console\
--include-data-dir=PyQt5/Qt5=PyQt5/Qt5 \
calculator.py
或:
python3 -m nuitka --follow-imports --enable-plugin=pyside6 --standalone main.py
打包PyQt5应用:
~/anaconda3/bin/python -m nuitka --recurse-all --recurse-directory=./ main.py
示例: 打包PySide6应用
mkdir pyside6nuitka
cd pyside6nuitka/
python3 -m venv env
source env/bin/activate
pip install nuitka
pip install pyside6
pip list
Package Version
---------- --------
Nuitka 0.6.16.2
pip 20.2.3
PySide6 6.1.2
setuptools 49.2.1
shiboken6 6.1.2
写一个简单的应用:
import sys
from PySide6.QtWidgets import QApplication
from PySide6.QtWidgets import QMainWindow
from PySide6.QtGui import QScreen
app = QApplication(sys.argv)
mainwindow = QMainWindow()
mainwindow.setGeometry(0, 0, 800, 600)
mainwindow.show()
app.exec()
打包:
python3 -m nuitka --follow-imports --enable-plugin=pyside6 --standalone main.py
Nuitka will make use of ccache to speed up repeated compilation.
Is it OK to download and put it in '/Users/loek/Library/Application Support/Nuitka/ccache/v4.2.1'.
No installer needed, cached, one time question.
Proceed and download? [Yes]/No
选No,然后:
Nuitka:INFO: Successfully created 'main.dist/main'.
执行:main.dist/main
发现报错:
ImportError: dlopen(../dev/pyside6nuitka/./main.dist/PySide6/QtWidgets.so, 2): Library not loaded: @rpath/QtQml.framework/Versions/A/QtQml
Referenced from: ..dev/pyside6nuitka/main.dist/libpyside6.abi3.6.1.dylib
Reason: image not found
进入main.dist/PySide6,将venv环境中的pyside6nuitka/env/lib/python3.9/site-packages/PySide6/拷到main.dist/PySide6下即可。
再运行就正常了。
参考链接
- PyOxidizer
- Nuitka: Nuitka is a Python compiler written in Python.
- py2exe
- py2app