Pyinstaller是用于打包python项目的一个工具, 能够将项目代码打包成可执行文件, 在其余机器上使用.
通俗的说, 没打包的时候运行程序的命令是:python3 main.py arg1 arg2 ...
.那么打包完后能够这么执行./main arg1 arg2 ...
, main是你打包后的可执行文件名. arg1 arg2 ...
就是运行程序的参数,能够是sys.argv
, argparser
或者其它命令行参数工具.python
就我的使用状况来看,Pyinstaller有两大特色(未必是优势):git
就官方教程和网上数以万计的博客来看, 打包流程真的很简单, 首先把你的祖传代码改好并肯定好入口文件(这里假设是main.py
),而后执行命令pyinstaller -F main.py
最后去dist
目录拿可执行文件main(windows下多是exe后缀)
就能够愉快运行了.github
可是在这个简单的过程当中,确总会出各类错误,其中主要缘由是使用Pyinstaller的场景每每都是工业界部署, 须要打包一个项目或工程, 项目复杂必然容易出错.docker
本人自参加工做以来,常常参与打包部署, 遇到了不少坑, 所以专门写了这篇文章来分享靠谱的打包流程和常见Bug解决办法. 本人主要工做环境是Linux, 所以本文对Linux下的打包更具指导意义.编程
本文主要分红两部分,第一部分讲通用靠谱的打包流程, 这是我无数次打包总结的一套流程, 但愿能帮助到你们.
第二部介绍Pyinstaller打包常见Bug和相关Tips.小程序
打包环境须要安装pyinstaller库和setuptools库, pip install
便可.
这里强烈建议使用 pyinstaller 3.5版本和setuptools 44.0版本,不然有必定概率会出现bug.
pyinstaller高版本可能会在你使用hooks时报错.
setuptools高版本可能会在你打包过程当中出现pkg_resources.py2_warn
的错误windows
一般状况下写好代码,装好必备库基本就能够执行pyinstaller -F main.py
了.可是考虑到项目复杂要作不少配置, 咱们先来生成一个打包配置文件, 执行命令pyi-makespec -F main.py
, 而后你就会在main.py的同级目录下看到main.spec文件. 这个文件的主要做用就是指定打包的各类配置, 下面贴一份一个spec文件内容:api
# -*- 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
. 它们的主要功能我都加在了注释里, 下面我会详细讲述这个四个选项应该如何配置,这也是本文的重要内容之一.安全
binaries用于添加程序运行时所需的一些动态连接库文件,举个例子你打包后的程序报错说找不到xxx.so
文件,那么你能够先经过find命令找到这个文件地址而后把它放到binaries
列表里面:函数
binaries=['/home/xx/xxx/xx.so']
若是你的打包程序须要播放好多音频文件(假设都在resources
目录内), 那你就能够把这个音频文件夹地址放到datas
列表中,格式以下:
datas = [('./resources/','songs')]
除了指定音频文件目录,咱们还加了一个字符串songs
,这是表示你的可执行程序释放文件时把resources
文件夹里的内容释放到songs
文件夹里.一般状况下执行可执行程序时, 程序会在/tmp
目录创建一个_MEI
开头临时目录,并将程序运行所需文件都释放到这里,因此你全部的音频文件均可以在这个临时目录里的songs
文件夹找到.
写到这里各位应该会发现一个关键点: 你的代码须要判断是本身是如何被执行的, 经过打包后的可执行程序执行的?仍是经过main.py执行的?, 那否则无法肯定资源访问目录, 解决办法: 若是getattr(sys,"frzozen",False)
为真,则是在可执行文件里执行的,而后经过sys._MEIPASS
获取上文提到的临时文件目录.
最后,本人不建议将数据打包到程序中,代码和数据分离是编程基本原则,数据加在可执行程序里,不只让程序体积变大,还会使修改\替换数据变的复杂(具体修改方法取决于你的代码加载文件机制,一个骚操做是趁程序释放文件之时,迅速定位目录位置进行文件替换>~<), 因此能分离就分离吧!
若是你的程序报xxx模块找不到的错误,那么每每就是某个程序隐式调用了该模块,使得pyinstaller没有扫描到. 这个时候咱们就把他显示加进去,举个例子,程序报错找不到numpy库,那么咱们就作以下操做:
hiddenimports=['numpy']
有时候不只要把xx库加进去,还要把xx.yy加进去,好比 hiddenimports=['tensorflow','tensorflow.contrib']
,
具体根据报错信息来肯定
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, 这也是为了减少程序体积.
若是你把上述配置都作好了,那么恭喜你,基本上打包和运行就很难出错了.
打包命令是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和解决思路以及我的打包经验, 供你们参考:
not found
或import error
的错误,基本经过hiddenimports
,hooks
和binaries
解决.(这个错误基本占了Pyinstlaller全部报错的 90%....)struct.error: 'i' format requires -2147483648 <= number <= 2147483647
是指你的打包文件太大了,超出2GB限制了,详情看这个issue. 解决方法,要么精简模块,要么慎用hooks功能,别把啥东西都往程序里塞,这也是建议优先hiddenimports的缘由.Qt failed
的相关信息不用理会,和python的图形界面编程有关最后感谢各位阅读, 但愿能帮到大家.
文章能够转载, 但请注明出处: