pyinstaller 介绍
文章目录
- 介绍
- 命令行参数
- 使用spec文件打包
- 运行时信息
- 项目打包案例
介绍
参考文档
pyinstaller 是一个将python应用及其依赖库打包为可执行软件的三方库,在没有python开发环境的系统中仍可以直接运行。
- 支持Windows(win8+), macOS, and Linux,在windows下打包的可执行程序只能在windows下运行,是面向平台的打包;
- 安装 pip install -U pyinstaller , 或者指定某个版本
pyinstaller==6.15.0 - 打包原理
- 分析依赖,从py应用的入口程序开始 递归地分析 所有的导入依赖包;
- 收集依赖,将python解释器、依赖包、py应用项目代码等拷贝到一个目录中,生成一个压缩文件(如.exe);
- 创建引导程序,创建可执行程序的引导程序;
- 解压执行,在执行打包后的可执行程序时,默认将压缩文件解压到Temp目录下,并从入口开始执行
- 简单的打包
- 命令行进入到项目目录下,
pyinstaller main.py
- 命令行进入到项目目录下,
- 两种打包模式
- 单个文件,通过参数–onefile, -F指定,生成一个可执行文件,如windows下的exe,所有的项目脚本及依赖包都会打包到一个可执行文件中,启动执行会比单目录模式稍慢;执行该单文件exe时,会默认在Temp目录下解压出所有的依赖包、python解释器等(_MEIxxx唯一目录),并从引导程序开始执行;
- 单个目录,通过参数–onedir, -D指定,生成一个目录(包含所有的依赖)和一个可执行文件(包含项目文件);
- 适合debug调试问题
- 如果仅更改了项目脚本文件,只需重新发布exe程序即可,不用更新_internal目录;但是如果依赖也更新了,那么必须重新发布整个包(exe+_internal目录);
- exe中的启动引导程序bootloader创建一个临时的python环境,使用python解释器开始执行所有的python脚本,从_internal目录中找到所有的依赖包;
- 隐式导入问题,在python脚本中的隐式导入,pyinstaller无法感知,必须通过其他的参数来处理;
- _import_()
- importlib.import_module()
- 运行时操作sys.path
- 不会打包项目的py源码,而是打包编译后的pyc字节码,pyc原则上是可以反编译的,为了代码逻辑的安全性,最好使用Cython将py源码转为C代码,进而编译为机器码,更加安全;
命令行参数
pyinstaller main.py 打包时,可以指定一系列的命令行参数;
-
-h 帮助信息
-
- -distpath DIR,打包产物存储目录
-
–workpath 工作目录
-
–clean,构建之前清理缓存和临时文件;
-
–log-level LEVEL 构建时是日志级别;
-
-D, --onedir,单目录打包模式;
-
-F, --onefile 单文件打包模式;
–specpath spec文件存储的目录,默认在当前目录
-n, --name 打包应用的名称
–contents-directory 单目录模式的目录名; -
–add-data source:dest, 将项目中的数据文件打包到根目录下的dest目录;例如…/resources:resources,将上级目录下的resources目录打包到根目录下的resources目录;./db.sqlite:. 将当前目录下的db文件打包到_internal或者_MEIxx目录下(根目录)
-
–add-binary source:dest, 添加二进制文件;
-
-p, --paths DIR,依赖包的搜索目录,等价于spec文件中的pathex
-
–hidden-import, --hiddenimport MODULENAME
-
–collect-submodules MODULENAME
Collect all submodules from the specified package or module. This option can be used multiple times. -
–collect-data module_name, 从模块中搜索数据文件;
-
–collect-binaries module_name; 从模块中搜索二进制文件;
-
–collect-all module_name,从模块中搜索数据文件、二进制文件、子模块;
-
–copy-metadata packagename,复制包的元数据;
-
–recursive-copy-metadatapackagename
-
–additional-hooks-dir hookspath,搜索钩子的目录;
-
–runtime-hook hook ,运行时的钩子
-
–exclude-module module_name, 排除的模组
-
–splash IMAGE_FILE,启动画面的图片
-
-w 没有控制台窗口;-c 有控制台窗口;
案例:
pyinstaller --noconfirm --log-level=WARN -F -w--add-data="README:." --add-data="image1.png:img" --add-binary="libfoo.so:lib"--hidden-import=secret1--hidden-import=secret2--icon=..\MLNMFLCN.ICOmyscript.py# 也可以在python脚本中运行
import PyInstaller.__main__PyInstaller.__main__.run(['my_script.py','--onefile','--windowed'
])
使用spec文件打包
第一次执行pyinstaller main.py时会在当前目录下生成一个.spec文件,编辑该文件后,可以直接使用.spec文件进行打包。
from PyInstaller.utils.hooks import collect_all, collect_data_filesdatas = [('src/README.txt', '.')
]
binaries = =[ ( '/usr/lib/libiodbc.2.dylib', '.' ) ]
hiddenimports = ["pydantic.deprecated.decorator"]
sklearn_data = collect_all("sklearn")
datas += sklearn_data[0]
binaries += sklearn.data[1]
hiddenimports += sklearn.data[2]a = Analysis(['main.py'],pathex=['/xxdir'], # 模块的搜索目录binaries=binaries, # 包含二进制文件datas=datas , #包含数据文件 hiddenimports=hiddenimports, # 隐式导入hookspath=None,runtime_hooks=None,excludes=None) # 排除的模块,不打包
pyz = PYZ(a.pure)
exe = EXE(pyz,... )
coll = COLLECT(...)
运行时信息
- 区分程序运行时是否为打包,sys.frozen
import sys# 打包
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):# _MEIPASS 就是_internal根目录或者单文件的解压目录_MEIXXX # sys.executable 可执行文件的目录print('running in a PyInstaller bundle')
else:print('running in a normal Python process')# 模块的路径
# __file__, sys._MEIPASS + 'mypackage/mymodule.py'
# main.py脚本被打包在sys._MEIPASS根目录下,内部的__file__为sys._MEIPASS + main.py
# 所以打包后读取main.py同级目录下的数据库文件,如下
# db_path = os.path.join(sys._MEIPASS, "db.sqlite")
# 也可以在main.py脚本中通过__file__获取该模块的绝对路径,然后获取根目录,再拼接数据库文件# --add-data="./db.sqlite:." 打包到sys._MEIPASS根目录中
# --add-data="../path/to/file.dat:." 打包到sys._MEIPASS根目录中
- sys.executable, 可执行文件路径
- 打包前是python解释器的路径;
- 打包后是exe可执行程序的路径;
