草庐IT

python使用pyinstaller打包项目文件

xuexishiwochengz 2023-04-09 原文

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

目录

  Pyinstaller基本使用方法

  Python项目的打包方法

    1.spec文件生成

    2.spec文件配置

    3.使用spec执行打包命令

  Python模块的打包问题

  冻结打包路径

  其它问题

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目录下,包括文本文件,视频文件等等。

    1.spec文件生成
    为了进行自定义配置的打包,首先需要编写打包的配置文件.spec文件。当使用pyinstaller -d xxx.py时候会生成默认的xxx.spec文件进行默认的打包配置。通过配置spec脚本,并执行pyinstaller -d xxx.spec完成自定义的打包。

    通过生成spec文件的命令,针对代码的主程序文件生成打包对应的spec文件

        pyi-makespec -w xxx.py
    打开生成的spec文件,修改其默认脚本,完成自定义打包需要的配置。spec文件是一个python脚本,其默认的结构如下例所示

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

# -*- mode: python -*-

block_cipher = None

= Analysis(['fastplot.py'],

pathex=['D:\\install_test\\DAGUI-0.1\\bin'],

binaries=[],

datas=[],

hiddenimports=[],

hookspath=[],

runtime_hooks=[],

excludes=[],

win_no_prefer_redirects=False,

win_private_assemblies=False,

cipher=block_cipher)

pyz = PYZ(a.pure, a.zipped_data,

cipher=block_cipher)

exe = EXE(pyz,

a.scripts,

exclude_binaries=True,

name='fastplot',

debug=False,

strip=False,

upx=True,

console=False )

coll = COLLECT(exe,

a.binaries,

a.zipfiles,

a.datas,

strip=False,

upx=True,

name='fastplot')

  


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

    Analysis以py文件为输入,它会分析py文件的依赖模块,并生成相应的信息

    PYZ是一个.pyz的压缩包,包含程序运行需要的所有依赖

    EXE根据上面两项生成

    COLLECT生成其他部分的输出文件夹,COLLECT也可以没有

2.spec文件配置
  首先给出举例python项目的spec文件配置

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

# -*- mode: python -*-

import sys

import os.path as osp

sys.setrecursionlimit(5000)

block_cipher = None

SETUP_DIR = 'D:\\install_test\\FASTPLOT\\'

= Analysis(['fastplot.py',

'frozen_dir.py',

'D:\\install_test\\FASTPLOT\\lib\\app\\start.py',

'D:\\install_test\\FASTPLOT\\lib\\models\\analysis_model.py',

'D:\\install_test\\FASTPLOT\\lib\\models\\datafile_model.py',

'D:\\install_test\\FASTPLOT\\lib\\models\\data_model.py',

'D:\\install_test\\FASTPLOT\\lib\\models\\figure_model.py',

'D:\\install_test\\FASTPLOT\\lib\\models\\time_model.py',

'D:\\install_test\\FASTPLOT\\lib\\models\\mathematics_model.py',

'D:\\install_test\\FASTPLOT\\lib\\views\\constant.py',

'D:\\install_test\\FASTPLOT\\lib\\views\\custom_dialog.py',

'D:\\install_test\\FASTPLOT\\lib\\views\\data_dict_window.py',

'D:\\install_test\\FASTPLOT\\lib\\views\\data_process_window.py',

'D:\\install_test\\FASTPLOT\\lib\\views\\data_sift_window.py',

'D:\\install_test\\FASTPLOT\\lib\\views\\mathematics_window.py',

'D:\\install_test\\FASTPLOT\\lib\\views\\para_temp_window.py',

'D:\\install_test\\FASTPLOT\\lib\\views\\mainwindow.py',

'D:\\install_test\\FASTPLOT\\lib\\views\\paralist_window.py',

'D:\\install_test\\FASTPLOT\\lib\\views\\plot_window.py'],

pathex=['D:\\install_test\\FASTPLOT'],

binaries=[],

datas=[(SETUP_DIR+'lib\\icon','lib\\icon'),(SETUP_DIR+'data','data')],

hiddenimports=['pandas','pandas._libs','pandas._libs.tslibs.np_datetime','pandas._libs.tslibs.timedeltas',

'pandas._libs.tslibs.nattype''pandas._libs.skiplist','scipy._lib','scipy._lib.messagestream'],

hookspath=[],

runtime_hooks=[],

excludes=[],

win_no_prefer_redirects=False,

win_private_assemblies=False,

cipher=block_cipher)

pyz = PYZ(a.pure, a.zipped_data,

cipher=block_cipher)

exe = EXE(pyz,

a.scripts,

exclude_binaries=True,

name='fastplot',

debug=False,

strip=False,

upx=True,

console=True)

coll = COLLECT(exe,

a.binaries,

a.zipfiles,

a.datas,

strip=False,

upx=True,

name='fastplot')

  


a) py文件打包配置

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

b) 资源文件打包配置

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

c)Hidden import配置

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

d)递归深度设置

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

1

2

import sys

sys.setrecursionlimit(5000)

  

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

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

  1.PyQt plugins缺失

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

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

  打包后程序运行后,使用png格式的图标可以正常显示,但使用的ico格式图标不显示(对于所有图标和关联文件都无法使用的情况涉及到路径问题,后文会另外解释)。

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

  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整个目录复制到打包结果目录。

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

  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
---------------------

有关python使用pyinstaller打包项目文件的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  7. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  8. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  9. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  10. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

随机推荐