Pyinstaller 打包发布经验总结

使用Pyinstaller打包Python项目包含了大量的坑,这篇文章总结实践得到的Pyinstaller打包经验。本文的例子为Python3.6代码,Pyinstaller3.4,在windows下打包为64位和32位版本。

Pyinstaller基本使用方法

Pyinstaller可以通过简单的命令进行python代码的打包工作,其基本的命令为:

pyinstaller -option xxx.py

options的详情可参考官方帮助文档https://pyinstaller.readthedocs.io/en/stable/usage.html

这边只介绍用到的option:-d生成一个文件目录包含可执行文件和相关动态链接库和资源文件等;-f仅生成一个可执行文件

-D, --onedir Create a one-folder bundle containing an executable (default)
-F, --onefile Create a one-file bundled executable.

对于打包结果较大的项目,选用-d生成目录相比单可执行文件的打包方式,执行速度更快,但包含更加多的文件。本文的例子选中-D方式打包。

Python项目的打包方法

以一个多文件和目录的Python项目为例,项目文件包含:1.Python源代码文件;2.图标资源文件;3.其它资源文件

以图中项目为例,Python源代码文件在多个目录下:bin, lib\app, lib\models, lib\views;图标资源文件在lib\icon目录下;其它资源文件在data目录下,包括文本文件,视频文件等等。

Visual C++ run-time .dlls包含

针对在Windows<10发布使用,且Python>=3.5的情况,Pyinstaller打包的程序可能会出现不包含Visual C++ run-time .dlls的情况,Python>=3.5需要使用Visual Studio 2015 run-time,也就是Universal CRT,这些runtime在Win10本身或Win7到Win8.1版本的更新包里,但程序打包后使用的系统里并不一定安装了。因此需要参考Universal CRT的建议,应用以下的方法解决这个问题:

Build on Windows 7 which has been reported to work.

Include one of the VCRedist packages (the redistributable package files) into your application's installer. This is Microsoft's recommended way, see “Distributing Software that uses the Universal CRT“ in the above-mentioned link, numbers 2 and 3.

Install the Windows Software Development Kit (SDK) for Windows 10 and expand the .spec-file to include the required DLLs, see “Distributing Software that uses the Universal CRT“ in the above-mentioned link, number 6.

Python模块的打包问题

程序调用的很多包,在打包时候可能会出现一些问题,针对这写问题需要做一些处理才能保证打包的程序正常执行。

1.PyQt plugins缺失

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

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

2.动态链接库缺失问题

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

ImportError: DLL load failed: 找不到指定的模块

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

binaries=[('C:\\Program Files\\Python36-32\\Lib\\site-packages\\scipy\\extra-dll','.')]

Analysis下的binaries是为打包文件添加二进制文件,缺失的动态链接库可以通过这种方式自动加入到打包路径中。

3.窗体风格变化问题

在某些情况下,如在精简环境下的python程序打包中,执行打包后的程序会出现窗体风格变为老式的win风格,这是由于打包时候PyQt的styles动态库没有找到。因此只需要在Python 目录下找到 Lib\site-packages\PyQt5\Qt\plugins\styles,将styles整个目录复制到打包结果目录。

4.UnicodeDecodeError

当打包时出现类似错误时:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xce in position 122

可在打包的命令行中输入chcp 65001设置命令行显示utf-8字符,然后再执行打包命令。或者,修改pyinstaller包下的compat.py,根据报错对应的行将

out = out.decode(encoding)

改为

out = out.decode(encoding, 'replace')

冻结打包路径

执行打包后的程序,经常会出现程序使用的图标无法显示,程序使用的关联文件无法关联。或者,在打包的本机上运行正常,但是将打包后的程序放到其它机器上就有问题。这些现象都很有可能是由程序使用的文件路径发生改变产生的,因此在打包时候我们需要根据执行路径进行路径“冻结”。

1.使用绝对路径

在python代码中使用绝对路径调用外部文件可以保证打包时候路径可追溯,因此在本机上运行打包后程序基本没问题。但是当本机上对应路径的资源文件被改变,或者将打包程序应用到别的机器,都会出现搜索不到资源文件的问题。这种方式不是合适的打包发布python软件的方式。

2.使用冻结路径

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

# -*- coding: utf-8 -*-
"""
Created on Sat Aug 25 22:41:09 2018
frozen dir
@author: yanhua
"""
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 frozen_dir
SETUP_DIR = frozen_dir.app_path()
 
FONT_MSYH = matplotlib.font_manager.FontProperties(
        fname = SETUP_DIR + r'\data\fonts\msyh.ttf',
        size = 8)
 
DIR_HELP_DOC = SETUP_DIR + r'\data\docs'
DIR_HELP_VIDEO = SETUP_DIR + r'\data\videos'

通过冻结路径,使用了基准目录下的data目录下的fonts, docs, videos。

主程序中也做了类似的调整,改变其设置路径方法

import frozen_dir
 
SETUP_DIR = frozen_dir.app_path()+r'\lib'
sys.path.append(SETUP_DIR)

使用这样的方法进行打包,打包后的可执行程序就可以在其它机器上运行。

其它问题

由于操作系统和运行环境的不同,pyinstaller打包中还可能遇到很多其它问题,最后总结一些我在打包中遇到的其它坑:

1.权限问题

通常时在打包时出现的某些文件拒绝访问或没有权限执行某些操作等。解决这个的方法一般有这几个方面:

a)使用管理员权限运行cmd或其它命令行窗口

b)关闭杀毒软件

c)使用完全权限的管理员账户

2.中文路径

pyinstaller打包后的路径使用中文没有问题,不过为了减少打包时候出错的可能,尽量将打包使用的资源文件和代码文件路径设置为英文。

3.打包后文件的大小

通常python打包为可执行文件都会得到一个较大的包,这是无法避免的,但是我们还是可以通过一些方法来尽量精简打包后的执行程序:

a)在代码中减少不必要的import,如from xxx import *

b)在精简的运行环境(如原生python环境)下打包,缺什么包就下什么包,避免不必要的python包被打包入程序。尤其是anaconda这样的集成环境下打包的结果会大很多。

c)使用UPX

到此这篇关于Pyinstaller 打包发布经验总结的文章就介绍到这了,更多相关Pyinstaller 打包发布内容请搜索来客网以前的文章或继续浏览下面的相关文章希望大家以后多多支持来客网!