首页 > Python资料 博客日记
python嵌入式打包,打包新姿势!打包速度比pyinstaller还快哦
2024-09-12 19:00:05Python资料围观73次
今天给大家介绍一种python程序的打包方式,使用python嵌入式解释器来打包程序。
本打包姿势融合了较多本人的理解,使得其最终实现的效果优于常规的pyinstaller及nuitka。
写在前面,本文充分借鉴了韦易笑大佬的文章,非常感谢大佬的分享。
怎么样打包 pyqt 应用才是最佳方案?或者说 pyqt 怎样的发布方式最优?
首先需要介绍,什么是嵌入式打包?
嵌入式打包指的是使用python官方提供的python embeddable解释器,来打包一个独立的python环境。
注意:本打包方法仅适用于python3.5版本及以上。
示例程序
接下来介绍本文所用的示例程序,为了简单,本文仅使用numpy生成数据,pandas处理数据,matplotlib绘图,pyqt5显示结果,都是非常常见的第三方依赖。
import sys
import numpy as np
import pandas as pd
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class MplCanvas(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot()
# 初始化父类
super(MplCanvas, self).__init__(fig)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
# 创建MplCanvas实例作为窗口中的中央部件
self.canvas = MplCanvas(self, width=5, height=4, dpi=100)
# 生成数据并绘制
x_values = np.linspace(0, 2 * np.pi, num=200)
y_values = np.sin(x_values)
df = pd.DataFrame({'x': x_values, 'sin(x)': y_values})
self.canvas.axes.plot(x_values, y_values, label='Sine Function')
# 设置图表标题、坐标轴标签等
self.canvas.axes.set_xlabel('X axis (x)')
self.canvas.axes.set_ylabel('Y axis(sin(x))')
self.canvas.axes.set_title('y = sin(x)')
self.canvas.axes.legend()
# 布局管理
layout = QVBoxLayout()
layout.addWidget(self.canvas)
self.text=QLabel(str(df.info))
layout.addWidget(self.text)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
if __name__ == '__main__':
app = QApplication(sys.argv)
main_win = MainWindow()
main_win.show()
sys.exit(app.exec_())
程序运行结果如下,包含个曲线图以及一段pandas的dataframe信息。
操作步骤
接下来就不废话了,直接上操作步骤。
1,下载pystand项目中的pystand程序,这是一个壳,用来调用python解释器运行代码。
只需要看这两个就行,一个是py38-x64,一个是py38,分别对应64位解释器和32位解释器,同时也对应了64位的pystand及32位的pystand,请注意这俩一定要对应上。不能用32位的pystand去运行64位的python解释器。
2,本文以64位为例,下载PyStand-py38-x64.7z。下载到本地后,它应该长这样。
解释一下,runtime文件夹里面放python embeddable解释器,这里面自带的是python3.8.10。
site-packages里面放第三方库。
pystand.exe是一个c语言写的壳,双击即可运行pystand.int文件中的代码。
pystand.int中保存的是python代码,你可以理解为main.py文件,这里需要改为程序的入口,后面会详细介绍。
你可以选择将runtime文件夹里面的内容删掉,自己去官网下载其他版本的python embeddable解释器,我这里以python 3.10为例,去官网下载python-3.10.5-embed-amd64.zip,并替换runtime文件夹中的文件。ok,现在python解释器已经配置完毕。
3,将你的项目所依赖的虚拟环境下的sitepackage文件夹下所有内容copy到pystand的sitepackage文件夹下,请注意,如果你的项目所依赖的虚拟环境并不是特别的干净,比如还有项目根本没用到的scipy库等,我建议你重新新建虚拟环境,安装好依赖后拷贝。
4,在pystand目录下,新建script目录,将程序代码拷贝到里面去。并对代码入口做简要修改,注释掉 if __name__ == '__main__':,新建入口函数,名称随意,这里以start为例。
def start():
app = QApplication(sys.argv)
main_win = MainWindow()
main_win.show()
sys.exit(app.exec_())
# if __name__ == '__main__':
# app = QApplication(sys.argv)
# main_win = MainWindow()
# main_win.show()
# sys.exit(app.exec_())
5,修改pystand.int中的代码,以文本编辑器打开该文件,直接清空内容,输入以下代码。这样就表示,从start函数启动程序。
但是第4步我们将main.py文件扔到了scripts文件夹下,程序是不知道main.py文件在哪儿的,所以会报错:
from main import start.
ModuleNotFoundError: no module named "main"
from main import start
if __name__ == "__main__":
#运行入口函数
start()
所以我们需要告诉一下python,你需要去scripts目录找main模块,这里利用.pth文件。
新建main.txt文本文件(名称随意),里面写上scripts,保存,修改后缀名为.pth,将该文件放在pystand.exe同级目录下。
tips:你要是觉得放在pystand.exe同级目录显得很乱,你可以将新建的main.txt放在其他地方,里面的路径也可以写其他相对路径,如./scripts,../scripts等,这一块就不赘述了,自己问chatai吧。
6,双击pystand.exe,运行程序,可得到如期结果。到这儿,打包算初步完成。
!如果不能如期运行,可以使用cmd命令运行pystand.exe,可以查看到报错信息。
!如果需要自定义程序名字,需要同时修改pystand.exe及pystand.int两个文件,保持同名即可。
!如果觉得保持同名很麻烦,想允许用户自己随便改名,那么请将pystand.int改名为_pystand_static.int即可。
!如果需要自定义程序图标,请使用Resource Hacker 更换图标,非常简单。
!程序源代码是以源码形式放在script目录中的,建议使用nuitka批量将py文件转为pyd文件,提升运行速度的同时隐藏了源码。
!如果有需要,也可以自己编译pystand,修改原代码后彻底去除int文件。
nuitka转pyd命令:
对文件:切换至scripts文件夹下,python -m nuitka --module main.py
对文件夹:假设代码都放在app文件夹下。切换至scripts文件夹下,python -m nuitka --module app --include-package=app
打包到这里就算完成了。
先简要总结下此打包方法的优缺点:
优点:
①熟练后打包速度非常快,只需要简单的复制粘贴即可。
②不怕缺包漏包,嵌入式解释器自带了所有的标准库(在那个zip文件里,通过python310._pth导入),同时咱们拷贝了sitepackge文件夹,不存在pyinstaller及nuitka自动分析依赖所导致的缺文件问题。
③exe外壳用的是pystand,该项目已存续两年,用户很多,因此打包后报毒风险较低。
④软件启动速度,运行速度优于pyinstaller,与nuitka打包的程序也可以简单比比(当然比不上nuitka全编译)。
缺点:
sitepackage文件夹体积过大。
下面的步骤在于解决sitepackage体积过大的问题。
接下来就是本文打包姿势所表演的时候了。
7,查看打包体积,足足有338M,这太大了。
优化第一步,删除不需要的固有第三方库,请删除sitepackage文件夹下的pip,pip-info,wheel,wheel-info,setuptools,setuptools-info,合计6个文件夹,这大概是15M。删除rruntime文件夹里的get-pip.py文件,这文件居然有2M,是用来安装pip的,不需要它。
优化第二步,使用神秘脚本删除程序运行不需要的第三方库文件。
请到下面的链接获取,如果觉得好用,请大力宣传哦!
请仔细阅读该脚本使用说明,本文仅展示瘦身效果。
瘦身前:
瘦身后:
从338M减小到168M,减小了170M,50%的瘦身效果,删除的文件以原本的目录结构保存在site-packages_new文件夹中,删除的文件清单保存在site-packages_文件移动清单.txt中,可随时查看。
运行程序,程序运行无误,瘦身完美!
如果运行失败,请使用cmd命令运行pystand.exe程序,根据错误提示信息从文件移动清单中寻找缺失到的文件,还原即可。本部分内容请详细阅读脚本使用说明。
优化第三步,使用upx对打包后的文件夹中的dll,pyd,exe文件进行压缩,进一步压缩空间。
tips:使用upx后,并不能减小最终7z压缩包的体积,同时会增大程序运行时占用内存(大概就是翻倍)及增加程序启动时间。
from pathlib import Path
import os
dir_path = Path("E:\\pystand\\site-packages")
upx_path = Path('D:\\ProgramData\\anaconda3\\Scripts\\upx.exe')
command = f"{upx_path} --compress-icons=0 --lzma --strip-loadconf "
upack = f"{upx_path} -d "
exclude_files = ["qwindows.dll","qwindowsvistastyle.dll", "arrow.dll"]
extensions_to_find = ('.pyd', '.dll', '.exe')
def check_file(file_path: Path) -> bool:
for file in exclude_files:
if file in str(file_path):
return False
return True
# 遍历目录及其子目录,找到指定后缀名的文件
for ext in extensions_to_find:
files = list(dir_path.glob("**/*{}".format(ext)))
for file_path in files:
if check_file(file_path):
upx_command = command + str(file_path)
os.system(upx_command)
print("upx操作结束")
压缩结束后,查看打包体积,103M,相比一开始的338M,此时瘦身效果已达70%。
此时基本已经实现了较大的瘦身效果,下面的步骤收益不大了,感兴趣请继续往下看。
优化第四步,使用批处理命令命令依次实现以下3个目标操作,对sitepackage文件夹,①将.py文件编译为pyc文件,②删除py文件,③删除__pycache__文件夹。
SET "SOURCE_DIR=E:\pystand\site-packages"
SET "PYTHON_PATH=E:\pystand\runtime\python.exe"
setlocal enabledelayedexpansion
"%PYTHON_PATH%" -m compileall -b "%SOURCE_DIR%"
FOR /R "%SOURCE_DIR%" %%i IN (*.py) DO (
echo Deleting file: %%i
del "%%i"
)
echo All .py files have been deleted.
for /r "%SOURCE_DIR%" %%d in (__pycache__) do (
echo Removing directory: %%d
rmdir /s /q "%%d"
)
echo All __pycache__ directories have been removed.
pause
操作完成后,体积减小到84M,此时瘦身效果达75%。
优化第五步,这一步就不演示了,可以使用7z.sfx/BoxedApp Packer 2020将程序制作为单文件程序,可以使用in nosetup将程序制作为安装包,或直接7z压缩发给用户。使用7z压缩后,体积大概为36M左右。制作为单文件程序体积大概为36M,这个时候就可以跟pyinstaller的单文件模式对比一下了,绝对是秒杀。但是我绝对不推荐将程序打包发布为单文件程序,你看看你电脑上哪个大型软件是一个文件呢?
单文件制作工具获取
链接:https://pan.baidu.com/s/1o9jE9SJ9ecBzBT728-tQtA?pwd=vecq 提取码:vecq 复制这段内容后打开百度网盘手机App,操作更方便哦
群友反馈
加密源码
按照本文所推荐的打包方式,源码其实是放在script文件夹中,以python源码的形式存放的。现在我们来简单加密一下,不让别人随随便看见。原理就是把py文件编译为pyd文件,用于替代py文件。
注意:这个只能转换py文件,其余的资源文件或二进制文件保留原路径即可。资源文件路径我建议代码里通过工作路径+相对路径拼接
这里可能有几种情况:
①只有一个py文件,比如本文的main.py文件,该文件目前是放在scripts文件夹下,使用cd命令切换至script文件夹。然后使用nuitka命令
命令如下:
python -m nuitka --module main.py
完事后会生成main.pyd文件,将原来的main.py及__pycache__文件夹(这个文件夹如果有一定要删,里面是pyc文件,可以完美还原源码)删掉。
②只有一个代码文件夹,文件夹包含子文件夹或代码文件,名字假设为app。使用cd命令切换至script文件夹。然后使用nuitka命令
命令如下:
python -m nuitka --module app --include-package=app
完事后会生成app.pyd文件,将原来的app文件夹里面的源码及__pycache__文件夹(这个文件夹如果有一要删,里面是pyc文件,可以完美还原源码)删掉。
③多个同级目录代码文件及代码文件夹
那就只能重复使用上述①②情况下的命令编译,生成多个pyd(模块)文件。
pyd文件比pyc文件安全的多,它类似于dll文件,一般人很难破解,想逆向还原源码,那更是不可能。
但是pyd文件作为一个依赖模块,是可以直接通过dir函数查看其暴露的接口的,所以请将重要变量如key什么的匿名化(不能被import,dir出来)。
这里贴一下nuitka维护者关于pyd文件安全性的回复
当然pyd文件在专业的人手里,那还是有破解的可能,类似于dll文件逆向。如果还想继续加密,可以参考本文开头所引用的韦易笑大佬的文章中的代码加密办法。本人不擅长此道。
常见问题
大部分问题都可以在pystand项目issues页找到答案,下面简要贴出几个常见问题及解决方案。
1,multiprocessing相关代码打包后运行,重复开启多个窗口。
解决方案:
if not hasattr(sys, 'frozen'):
sys.frozen = True
multiprocessing.freeze_support()
2,目录含中文,报错no QT platform plugin
解决方案:pyside同理
import sys, os
import os.path
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = r'.\site-packages\PyQt5\Qt5\plugins' #### 这一行是新增的。用的是相对路径。
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
app = QApplication([])
3,打包含tkinter的程序
官方提供的嵌入式解释器并不包含pip工具,以及tkinter模块,下面的步骤是怎么补上这个tkinter模块。
注:本方法目的在于后续可以复用,所以并不按照官方的目录结构来。
①复制tkinter模块。从已经安装了tkinter模块的相同 python解释器环境中复制,该模块通常位于解释器的Lib文件夹下,将tkinter文件夹复制到pystand/runtime/lib文件夹下,注意,这里需要新建一个lib文件夹。
②复制tcl资源文件。复制tcl文件夹里面所有的文件,到pystand/runtime/lib文件夹下,tcl文件夹通常位于解释器的同级目录。
③复制二进制模块。复制_tkinter.pyd,tcl86t.dll,tk86t.dll三个文件到pystand/runtime/lib文件夹下,这三个文件通常位于解释器的DLLs文件夹下。
④修改路径。修改runtime文件夹里面的python310._pth文件,增加一行./lib。注意,这里有个点,代表同级目录的lib文件夹。
现在可以运行程序了。如果后续还需要打包tkinter模块。直接复制这里弄好的lib文件夹跟._pth文件到runtime文件夹下即可实现复用。
视频讲解
总结
本文实际上是对嵌入式打包这种打包方式的进一步完善,原来流行的嵌入式打包方法,其打包后sitepackage文件夹体积过于庞大,本文结合了瘦身工具,使得打包后的体积与pyinstaller/nuitka相比也毫不逊色,同时拥有了更强的性能与更快的启动速度。熟练后,打包速度也非常快。
下面简单总结了几种打包方式的优缺点,欢迎讨论。
总算写完了,下一篇文章打算讲讲嵌入式打包相对于pyinstaller打包,nuitka打包的优点,这里先卖个关子。只能用遥遥领先来形容。
附录
有打包相关问题咨询的可以加群,群里面大佬甚多
注意:
新群友进群后,按下面格式做自我介绍
比如:
版面:Python与模具(您的知乎号,B站,或者公众号,可以为无,方便交流)
行业:营销,模具,广告
模块:Sanic,PyQt,爬虫,后端,全栈
标签:
相关文章
最新发布
- 【Python】selenium安装+Microsoft Edge驱动器下载配置流程
- Python 中自动打开网页并点击[自动化脚本],Selenium
- Anaconda基础使用
- 【Python】成功解决 TypeError: ‘<‘ not supported between instances of ‘str’ and ‘int’
- manim边学边做--三维的点和线
- CPython是最常用的Python解释器之一,也是Python官方实现。它是用C语言编写的,旨在提供一个高效且易于使用的Python解释器。
- Anaconda安装配置Jupyter(2024最新版)
- Python中读取Excel最快的几种方法!
- Python某城市美食商家爬虫数据可视化分析和推荐查询系统毕业设计论文开题报告
- 如何使用 Python 批量检测和转换 JSONL 文件编码为 UTF-8
点击排行
- 版本匹配指南:Numpy版本和Python版本的对应关系
- 版本匹配指南:PyTorch版本、torchvision 版本和Python版本的对应关系
- Python 可视化 web 神器:streamlit、Gradio、dash、nicegui;低代码 Python Web 框架:PyWebIO
- 相关性分析——Pearson相关系数+热力图(附data和Python完整代码)
- Python与PyTorch的版本对应
- Anaconda版本和Python版本对应关系(持续更新...)
- Python pyinstaller打包exe最完整教程
- Could not build wheels for llama-cpp-python, which is required to install pyproject.toml-based proj