Pyinstaller打包通用流程
Pyinstaller打包通用流程
前言
什么是Pyinstaller
Pyinstaller是用于打包python项目的一个工具, 可以将项目代码打包成可执行文件, 在其他机器上使用.
通俗的说, 没打包的时候运行程序的命令是:python3 main.py arg1 arg2 ...
.那么打包完后可以这么执行./main arg1 arg2 ...
, main是你打包后的可执行文件名. arg1 arg2 ...
就是运行程序的参数,可以是sys.argv
, argparser
或者其它命令行参数工具.
就个人使用情况来看,Pyinstaller有两大特点(未必是优点):
- 部署方便, 目标机器可以没有各种繁杂的第三方python库, 甚至不需要python环境,你带着一个可执行程序就可以去部署了.
- 代码安全, 带着可执行程序去运行可以避免代码泄露, 但是如果要完全保护代码还要考虑防反编译.
我为什么写这篇文章
就官方教程和网上数以万计的博客来看, 打包流程真的很简单, 首先把你的祖传代码改好并确定好入口文件(这里假设是main.py
),然后执行命令pyinstaller -F main.py
最后去dist
目录拿可执行文件main(windows下可能是exe后缀)
就可以愉快运行了.
但是在这个简单的过程中,确总会出各种错误,其中主要原因是使用Pyinstaller的场景往往都是工业界部署, 需要打包一个项目或工程, 项目复杂必然容易出错.
本人自参加工作以来,经常参与打包部署, 遇到了很多坑, 因此专门写了这篇文章来分享靠谱的打包流程和常见Bug解决办法. 本人主要工作环境是Linux, 因此本文对Linux下的打包更具指导意义.
本文主要内容
本文主要分成两部分,第一部分讲通用靠谱的打包流程, 这是我无数次打包总结的一套流程, 希望能帮助到大家.
第二部介绍Pyinstaller打包常见Bug和相关Tips.
pyinstaller打包的流程
Step1 工具准备
打包环境需要安装pyinstaller库和setuptools库, pip install
即可.
这里强烈建议使用 pyinstaller 3.5版本和setuptools 44.0版本,否则有一定几率会出现bug.
pyinstaller高版本可能会在你使用hooks时报错.
setuptools高版本可能会在你打包过程中出现pkg_resources.py2_warn
的错误
Step2 配置打包项
通常情况下写好代码,装好必备库基本就可以执行pyinstaller -F main.py
了.但是考虑到项目复杂要做很多配置, 我们先来生成一个打包配置文件, 执行命令pyi-makespec -F main.py
, 然后你就会在main.py的同级目录下看到main.spec文件. 这个文件的主要作用就是指定打包的各种配置, 下面贴一份一个spec文件内容:
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['main.py'],
pathex=['/home'],
binaries=[], # 打包动态链接库文件(so或dll)
datas=[], # 打包程序需要的数据(文本\音视频等)
hiddenimports=[], # 一些难以打包进去的库放到里面(通常是复杂的库)
hookspath=[], # 指定hook文件夹,能够搜索添加库所需的所有文件
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='main')
spec文件内容还是挺多的,语法就是python,因此修改该文件的时候请遵循python语法. 内容虽多,不过真正常用的就四项分别是 binaries
,datas
,hiddenimports
和hookspath
. 它们的主要功能我都加在了注释里, 下面我会详细讲述这个四个选项应该如何配置,这也是本文的重要内容之一.
(1) binaries
binaries用于添加程序运行时所需的一些动态链接库文件,举个例子你打包后的程序报错说找不到xxx.so
文件,那么你可以先通过find命令找到这个文件地址然后把它放到binaries
列表里面:
binaries=['/home/xx/xxx/xx.so']
(2) datas
如果你的打包程序需要播放好多音频文件(假设都在resources
目录内), 那你就可以把这个音频文件夹地址放到datas
列表中,格式如下:
datas = [('./resources/','songs')]
除了指定音频文件目录,我们还加了一个字符串songs
,这是表示你的可执行程序释放文件时把resources
文件夹里的内容释放到songs
文件夹里.通常情况下执行可执行程序时, 程序会在/tmp
目录建立一个_MEI
开头临时目录,并将程序运行所需文件都释放到这里,所以你所有的音频文件都可以在这个临时目录里的songs
文件夹找到.
写到这里各位应该会发现一个关键点: 你的代码需要判断是自己是如何被执行的, 通过打包后的可执行程序执行的?还是通过main.py执行的?, 那不然没法确定资源访问目录, 解决办法: 如果getattr(sys,"frzozen",False)
为真,则是在可执行文件里执行的,然后通过sys._MEIPASS
获取上文提到的临时文件目录.
最后,本人不建议将数据打包到程序中,代码和数据分离是编程基本原则,数据加在可执行程序里,不仅让程序体积变大,还会使修改\替换数据变的复杂(具体修改方法取决于你的代码加载文件机制,一个骚操作是趁程序释放文件之时,迅速定位目录位置进行文件替换>~<), 所以能分离就分离吧!
(3) hiddenimports
如果你的程序报xxx模块找不到的错误,那么往往就是某个程序隐式调用了该模块,使得pyinstaller没有扫描到. 这个时候我们就把他显示加进去,举个例子,程序报错找不到numpy库,那么我们就做如下操作:
hiddenimports=['numpy']
有时候不仅要把xx库加进去,还要把xx.yy加进去,比如 hiddenimports=['tensorflow','tensorflow.contrib']
,
具体根据报错信息来确定
(4) hookspath
hookspath的作用是搜索某个库所需的文件并把他们添加到打包程序里. hookspath指定一个文件夹路径(通常情况下文件夹名就叫hooks),hooks文件夹里有若干python文件,以hook-库名格式命名,比如对于gevent, 就要命名为hook-gevent.py. 强烈推荐文件内容这么写:
from PyInstaller.utils.hooks import collect_all
def hook(hook_api):
packages = ['gevent']
for package in packages:
datas, binaries, hiddenimports = collect_all(package)
# hook_api.add_datas(datas) # 注释掉是因为通常用不到
# hook_api.add_binaries(binaries)
hook_api.add_imports(*hiddenimports)
简单解释下,借助collect_all
函数可以分析出这个库需要的所有文件和库, 以gevent为例, 执行
datas, binaries, hiddenimports = collect_all('gevent')
那么:
- 你将会获得该库所需的所有数据文件,比如
__greenlet_primitives.pxd
,__hub_local.pxd
等 - 你将会获得该库所需的所有二进制文件,比如
__greenlet_primitives.cp37-win_amd64.pyd
等 - 你将会获得该库所需的所有子模块, 比如
gevent.threadpool
,gevent._semaphore
等
然后借助上面代码中的hook_api.add_xxx
函数把他们添加进去,我注释掉了两行,是因为有时候这两行不需要,实战时可以根据报错按需添加.
可以看出hooks可替代hiddenimports, 但还是建议优先使用hiddenimports, 这也是为了减小程序体积.
Step3 打包&测试
如果你把上述配置都做好了,那么恭喜你,基本上打包和运行就很难出错了.
打包命令是pyinstaller -F main.spec
. 注意是spec文件, 不是py文件
然后去dist
目录里找到main
文件,最后./main args
测试即可.
另外附上被打包项目的目录结构:
ProjectName
│ main.py # 入口文件
│ main.spec # 配置文件
│
├─codes # 你的祖传代码
└─hooks # hook文件
└─────hook-gevent.py
└─────hook-tensorflow.py
常见Bug和相关Tips
下面是本人打包时遇到的一些bug和解决思路以及个人打包经验, 供大家参考:
- 遇到各种
not found
或import error
的错误,基本通过hiddenimports
,hooks
和binaries
解决.(这个错误基本占了Pyinstlaller所有报错的 90%....) - 遇到这种罕见错误:
struct.error: 'i' format requires -2147483648 <= number <= 2147483647
是指你的打包文件太大了,超出2GB限制了,详情看这个issue. 解决方法,要么精简模块,要么慎用hooks功能,别把啥东西都往程序里塞,这也是建议优先hiddenimports的原因. - 能用hiddenimports就别用hooks,减小体积
- 打包程序多输出调试信息方便定位bug.
- 打包前务必认真测试,打包后出了bug就要重新打包了
- 打包深度学习程序等的时间在十分钟左右,保持耐心
- tensorflow最好1.14,实测大于1.14会出问题
- 打包时会有
Qt failed
的相关信息不用理会,和python的图形界面编程有关 - Pyinstaller程序使用GPU务必保证环境变量设置的没问题
- 同样是Pyinstaller程序使用GPU的问题,打包环境和运行环境显卡驱动版本最好一致,那不然有可能用不了GPU(这个本人还未充分验证),如果真遇到了这个情况,欢迎使用nvdocker.
- 如果一些bug怎么都解决不了,尝试切换不同库版本(比如pyinstaller,setuptools和你的程序使用的库), 甚至在纯净的docker里打包试试
最后感谢各位阅读, 希望能帮到你们.
文章可以转载, 但请注明出处:
Pyinstaller打包通用流程的更多相关文章
- 安装PyInstaller打包python
安装PyInstaller 对于那些网络比较稳定,能够流畅使用pip源地址的用户,直接下面的命令就可以搞定: pip install pyinstaller 通常我们会下载源码包,然后进入包目录,执行 ...
- PyInstaller打包成exe可执行文件
PyInstaller 安装pyinstaller 对于那些网络比较稳定,能够流畅使用pip源地址的用户,直接下面的命令就可以搞定: pip install pyinstaller 通常我们会下载源码 ...
- pyinstaller打包python文件成exe(原理.安装.问题)
py文件打包成exe文件的方式一共有三种:py2exe.PyInstaller和cx_Freeze 本文分四个步骤来详讲如何用PyInstaller将py文件打包成exe文件 1. PyInstall ...
- Pyinstaller打包Pytorch框架所遇到的问题
目录 前言 基本流程 一.安装Pyinstaller 和 测试Hello World 二.打包整个项目,在本机上调试生成exe 三.在新电脑上测试 参考资料 前言 第一次尝试用Pyinstalle ...
- python3使用pyinstaller打包apscheduler出的错
本来只是想用Python做一个定时任务小工具在服务器上运行,可是服务器在隔离区,各种禁止上外网,使用pip导出列表那种下载库的方法不管用,导致Python的各种库都下不到,官网离线下载又各种缺依赖,好 ...
- PyInstaller打包步骤简记
pyinstaller 下载地址:http://www.pyinstaller.org/ 下载后用cmd进入解压文件夹 python setup.py install 安装. 最近用pyinstall ...
- pyinstaller打包第一个wxPython程序HelloWorld
pyinstaller 打包hello 7Mb ================= www.pyinstaller.org pip install pypiwin32 pip install pyin ...
- [python学习笔记] pyinstaller打包pyqt5程序无法运行
问题 pyinstaller打包的pyqt5程序在部分电脑上会失败.用户截图提示下边错误日志 无法定位程序输入点 ucrtbase.terminate 于动态链接库 api-ms-win-crt-ru ...
- iOS开发基础:最新的APP打包上架流程
之前有人留言让我更新部分文章,下面就为大家分享一下iOS的APP打包上架流程: 上传至apple developer 1.1 上传准备工作 更新上架和发布上架不同,在原始版本首次上架的时候就将描述文件 ...
随机推荐
- Python和Nose实现移动应用的自动化测试
今天跟大家聊的是Python和Nose实现移动应用的自动化测试,希望对你们有帮助,有说的不好的地方,还请多多指教! 采用Appium进行自动化的功能性测试最酷的一点是,你可以使用具有最适合你的测试工具 ...
- 数据结构与算法系列2 线性表 链表的分类+使用java实现链表+链表源码详解
数据结构与算法系列2.2 线性表 什么是链表? 链表是一种物理存储单元上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表的链接次序实现的一系列节点组成,节点可以在运行时动态生成,每个节点包括两个 ...
- laravel Application实例化后两个方法
laravel容器初始化registerBaseServiceProviders方法 上篇讲解了laravel容器的基本使用和原理,这篇继续Application构造方法中的registerBaseS ...
- private protected internal public
//C#中的访问修饰符: //private,私有访问修饰符,被private访问修饰符修饰的成员只有在当前类的内部可以访问,其他地方一律不能访问[类中成员,如果不写访问修饰符则默认都是私有的] // ...
- Mac 安装多个版本jdk
JDK默认安装路径为/Library/Java/JavaVirtualMachines 多版本安装后效果为: 设置 1.执行以下命令 cd ~ open -e .bash_profile #打开.ba ...
- C#转PHP
官方主页 https://github.com/isukces/cs2php 快速开始 http://www.cs2php.com/how-to-begin.htm#.W2rBhC2B3mI 如何在V ...
- jumpserver如何在远程时使用复制粘贴-windwos系统下
jumpserver堡垒机搭建好了,但是在使用的时候,有时候会出现远程下不能复制粘贴,这让体验十分不爽. 于是着手解决这个问题,附上参考链接,感谢大佬:http://itren.xiaolee.net ...
- ThreadLocal是什么?谈谈你对他的理解
1.ThreadLocal是什么 从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的.ThreadLocal为变 ...
- WebApi 接口传参接参
阅读目录 一.get请求 1.基础类型参数 2.实体作为参数 3.数组作为参数 4.“怪异”的get请求 二.post请求 1.基础类型参数 2.实体作为参数 3.数组作为参数 4.后台发送请求参数的 ...
- 对韩峰著《SQL优化最佳实践》P7 案例的质疑
事先申明下,我的DB环境是Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production,如果与作者环境不同而 ...