• Pyinstaller打包通用流程


    Pyinstaller打包通用流程

    前言

    什么是Pyinstaller

    Pyinstaller是用于打包python项目的一个工具, 可以将项目代码打包成可执行文件, 在其他机器上使用.
    通俗的说, 没打包的时候运行程序的命令是:python3 main.py arg1 arg2 ....那么打包完后可以这么执行./main arg1 arg2 ..., main是你打包后的可执行文件名. arg1 arg2 ...就是运行程序的参数,可以是sys.argv, argparser或者其它命令行参数工具.

    就个人使用情况来看,Pyinstaller有两大特点(未必是优点):

    1. 部署方便, 目标机器可以没有各种繁杂的第三方python库, 甚至不需要python环境,你带着一个可执行程序就可以去部署了.
    2. 代码安全, 带着可执行程序去运行可以避免代码泄露, 但是如果要完全保护代码还要考虑防反编译.

    我为什么写这篇文章

    就官方教程和网上数以万计的博客来看, 打包流程真的很简单, 首先把你的祖传代码改好并确定好入口文件(这里假设是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,hiddenimportshookspath. 它们的主要功能我都加在了注释里, 下面我会详细讲述这个四个选项应该如何配置,这也是本文的重要内容之一.

    (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 foundimport error的错误,基本通过hiddenimports,hooksbinaries解决.(这个错误基本占了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里打包试试

    最后感谢各位阅读, 希望能帮到你们.

    文章可以转载, 但请注明出处:

  • 相关阅读:
    CodeForces Gym 100935G Board Game DFS
    CodeForces 493D Vasya and Chess 简单博弈
    CodeForces Gym 100935D Enormous Carpet 快速幂取模
    CodeForces Gym 100935E Pairs
    CodeForces Gym 100935C OCR (水
    CodeForces Gym 100935B Weird Cryptography
    HDU-敌兵布阵
    HDU-Minimum Inversion Number(最小逆序数)
    七月馒头
    非常可乐
  • 原文地址:https://www.cnblogs.com/infgrad/p/13407545.html
Copyright © 2020-2023  润新知