zoukankan      html  css  js  c++  java
  • Python3中采用PyInstaller打包工程项目

    对自己完成的工程项目进行打包,因为是第一次尝试,踩了各种各样的坑。以防下次继续踩坑,把相关问题以及解决办法记录下来。此次打包采用Python3.6,PyInstaller3.6,Windows64位系统。接下来就是整篇文章的精华了。

    一、PyInstaller安装

      PyInstaller包的安装可以在Anaconda环境下以conda install pyinstaller进行安装,在PyCharm中可以通过pip install pyinstaller进行安装。安装成功后就可以着手进行打包了。当然打包需要用到以下一些相关命令了。

    -h,--help查看该模块的帮助信息
    -F,-onefile 产生单个的可执行文件
    -D,--onedir 产生一个目录(包含多个文件)作为可执行程序
    -a,--ascii 不包含 Unicode 字符集支持
    -d,--debug 产生 debug 版本的可执行文件
    -w,--windowed,--noconsolc 指定程序运行时不显示命令行窗口(仅对 Windows 有效)
    -c,--nowindowed,--console 指定使用命令行窗口运行程序(仅对 Windows 有效)
    -o DIR,--out=DIR 指定 spec 文件的生成目录。如果没有指定,则默认使用当前目录来生成 spec 文件
    -p DIR,--path=DIR 设置 Python 导入模块的路径(和设置 PYTHONPATH 环境变量的作用相似)。也可使用路径分隔符(Windows 使用分号,Linux 使用冒号)来分隔多个路径
    -n NAME,--name=NAME 指定项目(产生的 spec)名字。如果省略该选项,那么第一个脚本的主文件名将作为 spec 的名字

      常用到的命令为-F、-D、-i、-p、-w等,其中-i用于指定生成项目的图标,需要使用绝对路径。对于打包结果较大的项目,选用-d生成目录相比单可执行文件的打包方式,执行速度更快,但包含更加多的文件。本文的例子选中-D方式打包。

    二、Python项目打包

      打包分为单文件打包以及工程文件打包,单文件打包直接在命令窗口中采用pyinstaller -D filename.py,具体命令的添加可以参考表格中的命令。工程文件打包稍微有一些麻烦,主要是先生成主窗口的.spec文件,并修改相应的内容,最终执行.spec文件就可以逐步实现打包。以下是本次打包的工程目录,主要文件均在moleculeSystem,还包括img文件夹,tempFile文件夹等众多文件。

      

    1.SPEC文件的生成

      通过使用命令pyi-makespec -w xxx.py能够生成相应的xxx.spec文件,具体如下:

    # -*- mode: python ; coding: utf-8 -*-
    
    block_cipher = None
    
    a = Analysis(['mainWin.py'],
                 pathex=['D:\Python\untitled1\moleculeSystem'],
                 binaries=[],
                 datas=[],
                 hiddenimports=[],
                 hookspath=[],
                 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='mainWin',
              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='mainWin')

      spec文件中主要包含4个class: Analysis, PYZ, EXE和COLLECT.

    • Analysis以py文件为输入,它会分析py文件的依赖模块,并生成相应的信息
    • PYZ是一个.pyz的压缩包,包含程序运行需要的所有依赖
    • EXE根据上面两项生成
    • COLLECT生成其他部分的输出文件夹,COLLECT也可以没有

    首先给出举例项目的spec文件:

    # -*- mode: python ; coding: utf-8 -*-
    
    block_cipher = None
    SETUP_DIR = 'D:\Python\untitled1\'
    
    a = Analysis(['mainWin.py'],
                 pathex=['D:\Python\untitled1\moleculeSystem'],
                 binaries=[],
                 datas=[(SETUP_DIR+'img','img'),(SETUP_DIR+'temp_img','temp_img'),(SETUP_DIR+'mol\totalFile','mol\totalFile'),(SETUP_DIR+'tempFile','tempFile'),('D:\Anaconda\Anaconda3\Lib\site-packages\vtkmodules','vtkmodules'),(SETUP_DIR+'AtomsInfo.txt','AtomsInfo.txt'),(SETUP_DIR+'BondInfos.txt','BondInfos.txt')],
                 hiddenimports=['pkg_resources'],
                 hookspath=[],
                 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='mainWin',
              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='mainWin')

      

    1)py文件打包配置

    针对多目录多文件的python项目,打包时候需要将所有相关的py文件输入到Analysis类里。Analysis类中的pathex定义了打包的主目录,对于在此目录下的py文件可以只写文件名不写路径。如上的spec脚本,将所有项目中的py文件路径以列表形式写入Analysis,这里为了说明混合使用了绝对路径和相对路径。

    2) 资源文件打包配置

    资源文件包括打包的python项目使用的相关文件,如图标文件,文本文件等。对于此类资源文件的打包需要设置Analysis的datas,如例子所示datas接收元组:datas=[(SETUP_DIR+'img','img')]。元组的组成为(原项目中资源文件路径,打包后路径),例子中的(SETUP_DIR+'img','img')表示从D:Pythonuntitled1下的img文件夹文件打包后放入打包结果路径下的img目录。

    3)Hidden import配置

    pyinstaller在进行打包时,会解析打包的python文件,自动寻找py源文件的依赖模块。但是pyinstaller解析模块时可能会遗漏某些模块(not visible to the analysis phase),造成打包后执行程序时出现类似No Module named xxx。这时我们就需要在Analysis下hiddenimports中加入遗漏的模块,如例子中所示。

    4)递归深度设置

    在打包导入某些模块时,常会出现"RecursionError: maximum recursion depth exceeded"的错误,这可能是打包时出现了大量的递归超出了python预设的递归深度。因此需要在spec文件上添加递归深度的设置,设置一个足够大的值来保证打包的进行,即

    import sys
    sys.setrecursionlimit(5000)

    5)去除不必要的模块import

    有时需要让pyinstaller不打包某些用不到的模块,可通过在excludes=[]中添加此模块实现
    3.使用spec文件打包

    pyinstaller -D xxx.spec

    打包生成两个文件目录build和dist,build为临时文件目录完成打包后可以删除;dist中存放打包的结果,可执行文件和其它程序运行的关联文件都在这个目录下。

    三、打包出现的问题

    1.PyQt plugins缺失

    使用PyQt编写UI交互界面的python代码在进行打包时可能会出现一些特别的问题。

    执行使用了PyQt的打包程序,常会出现这样的错误,提示缺少Qt platfrom plugin “windows”,如下图

     

    打包后程序运行后,使用png格式的图标可以正常显示,但使用的ico格式图标不显示。这两个错误产生的问题都是因为打包时没有将PyQt相关的动态链接库目录生成到打包目录下,因此可以通过将这些需要的文件目录拷贝到打包生成目录下,解决plugin缺失问题。以使用PyQt5编写的python软件打包为例,完成打包后的结果目录下包含PyQt5文件夹,将PyQt5Qtplugins下的所有内容(如下图)拷贝到打包结果目录。这样就可以解决PyQt plugins缺失的问题。

     2.动态链接库缺失问题

    一般的,打包后可能会缺失某些动态链接库,造成执行程序出错,如

    ImportError: DLL load failed: 找不到指定的模块
    在打包过程中一般会有与此相关的warning提示(lib not found)无法找到这些动态链接库。例如在32位版本的打包中,可能会出现scipy模块相关的dll文件无法找到。这时就需要在打包的spec文件中指定动态链接库路径,使其关联到打包后的路径中。
    binaries=[('路径','.')]

    3.Failed to execute script pyi_rth_pkgres

    打包运行xxx.exe文件出现上述问题,是有可能因PyInstaller版本不是最新的造成的,可以通过GitHub网站https://github.com/pyinstaller/pyinstaller/archive/develop.zip下载,下载后解压到某一文件夹中,采用命令行的方式进入解压后的文件夹中,使用命令python setup.py install进行安装。

    4.Failed to execute script pyi_rth_certifi

    出现此错误的原因是缺少了ssl证书,因此可以从Python官网上下载相应python版本的压缩包,解压后将解压包中的_ssl.pyd(复制到Anaconda目录下DLLs下覆盖原文件),将

    libcrypto-1_1.dll、libssl-1_1.dll(复制到Anaconda根目录)即可解决问题。

    5.no modules named xxxx

    出现该问题是打包后的根目录中没有所需要的模块,因此可以将对应安装库中的文件复制到根目录中,此错误就不再出现。或者可以在hiddenimports中加入缺失的包。或者在datas中作同样的处理。

    6.Failed to execute script xxxx

    出现该问题是在引用同级目录中的文件时出现,在有python文件出增加__init__.py文件即可解决,该文件可什么都不写。

    7.路径冻结

    增加一个py文件,例如叫frozenPath.py

    import sys
    import os
     
    def app_path():
        """Returns the base application path."""
        if hasattr(sys, 'frozen'):
            # Handles PyInstaller
            return os.path.dirname(sys.executable)
        return os.path.dirname(__file__)

    其中的app_path()函数返回一个程序的执行路径,为了方便我们将此文件放在项目文件的根目录,通过这种方式建立了相对路径的关系。源代码中使用路径时,以app_path()的返回值作为基准路径,其它路径都是其相对路径。以本文中使用的python项目打包为例,如下所示:

    import frozenPath
    # 根目录路径
    appPath = frozenPath.app_path()
    background = QtGui.QPixmap(appPath+"/img/1.png")

    这样不论打包的文件放在哪台电脑上运行都不会出现路径错误。

    8.始终还有问题存在

    可以采用以下命令解决

    pyinstaller --hidden-import=pkg_resources -F xxxx.py

    9.顺便再记个PyQt5中QSplitter部件无法显示其他窗口的背景

    原因在于主窗口部件无法显示背景,子窗口部件能够显示,基于此可以解决这种问题。

            # 设置背景图片
            background = QtGui.QPixmap(appPath+"/img/1.png")
            palette1 = QtGui.QPalette()
            palette1.setBrush(self.backgroundRole(),QtGui.QBrush(background))
            self.handle.setPalette(palette1)
            self.handle.setAutoFillBackground(True)
            # 设置背景透明
            self.main_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)

    10.安装PyQt5

    使用豆瓣能够告诉下载

    下载PyQt5
    pip install PyQt5 -i https://pypi.douban.com/simple
    下载PyQt5-tools
    pip install PyQt5-tools -i https://pypi.douban.com/simple

    希望这些能够对还在打包程序路上挣扎的同志们有所帮助。

     

      

  • 相关阅读:
    从Java到C++——常量的使用规则
    LintCode 二叉树的遍历 (非递归)
    POJ 3592 Instantaneous Transference(强连通+DP)
    怎样给UINavigationBar加入button?
    《Spring技术内幕》笔记-第四章 Spring MVC与web环境
    HDU 4714 Tree2cycle(树型DP)
    hdu 1102 Constructing Roads(kruskal || prim)
    [Android随笔]内存泄漏以及内存溢出
    保存数据同一时候查询保存数据记录的ID
    8086的储存器编址
  • 原文地址:https://www.cnblogs.com/qhu-hjx/p/13036551.html
Copyright © 2011-2022 走看看