将python代码打包成一个app/exe
前言
打包的代码通常都需要写一个简单的界面,一般用 PyQt 来写。用 PyQt 写界面的方法请戳这里:PyQt5的安装及基本配置 PyQt5教程
python提供了几个用来打包的模块,主要有 py2app、py2exe、pyinstaller,其中第一个是用来打包来给 mac 用的,后两者是针对于 windows 系统。
关于 py2exe 和 pyinstaller 两者的比较:
对于 pyinstaller 和 py2exe 两种把 Python 文件打包成 exe 可执行文件的方法,都有各自的优缺点。但是最终目的都是为了在没有 Python 环境下的普通 Windows 系统的电脑中可直接运行。py2exe 的使用方法基本和 py2app 一样,但是本人操作时发现在 mac 中无法用 py2exe 打包成 exe,但是可以用 pyinstaller 打包成 exe,没有尝试过是否可以在 windows 环境下用 py2app 打包成 app。pyinstaller (-F指令下)生成的 exe 文件,集成了所需要的所有资源(所以 exe 文件 相对较大),可直接拷贝到其他电脑中使用。对于 py2exe 来说,限制就比较多了,它所需要用到的外部资源都在 dist 目录下,想要在其他电脑中使用就必须把整个 dist 文件夹都拷贝过去。而且经测试在 64 位机器生成的 exe 无法在 32 位机器上打开使用。
py2app打包
注:py2app 方法已在 Mac 环境下测试无误,windows 环境操作时如果遇到问题请自行Google,
一、安装py2app
sudo pip install py2app
二、进入要打包的文件所在的文件夹
cd 。。。。。。。。
三、生成setup.py文件,该文件用于写打包所需要的依赖
py2applet --make-setup xxx.py # xxx.py为项目的启动文件,之后生成的xxx文件就是双击执行的app文件
执行以后目录中会生成 setup.py 文件,用于写入依赖的库。
四、在 setup.py 文件中手动输入需要的依赖
如果项目很简单,没有导入第三方库和自建模块,可以忽略此步骤。
下面是setup.py文件的一个例子,手动输入的部分就是在 DATA_FILES 空列表里加自建模块的名字,在 OPTIONS 字典的 includes 对应的空列表中加第三方模块的名字
# python自带的库无需输入,第三方库和自己引入的自写模块需要输入 """ This is a setup.py script generated by py2applet Usage: python setup.py py2app """ from setuptools import setup APP = ['start.py'] #自写模块放在DATA_FILES列表中 DATA_FILES = ['xxx1.py','xxx2.py','xxx3.py'] # 第三方库放在OPTIONS下的includes对应的列表中 OPTIONS = { 'includes': ['sip', 'PyQt5.QtCore', 'PyQt5.QtWidgets'],} setup( app=APP, data_files=DATA_FILES, options={'py2app': OPTIONS}, setup_requires=['py2app'], )
五、生成app
# 自己开发,打包速度快。(因为本机安装了依赖库,所以可以直接运行) python setup.py py2app -A # 给其他没有 sdk 的电脑使用,包括 lib 库。(没有安装 sdk 的电脑使用,需要去掉 -A,将把所有的依赖全部打包。) python setup.py py2app
之后会生成 build 和 dist 两个文件夹,启动文件在 dist 下,双击就可以执行。
注:如果发现有问题,在重新进行上述步骤前最好先删除 build 和 dist 两个文件夹
rm -rf build dist
py2exe
首先声明,py2exe 在高版本的 python 环境下可能会出现不支持的情况,我在打包的时候只支持到 python3.4,不清楚目前支持到哪个 python 版本。
注:本人在 win10 下用 py2exe 打包的含有 PyQt5 写界面的程序无法正常运行,遇到的问题很多,如果程序中用到了 PyQt5,推荐选用 pyinstaller 打包
一、安装py2exe
pip install py2exe
二、进入项目目录
cd xxxxxxxxx
三、在项目根目录中自行创建setup.py文件
该文件的作用与 py2app 的 setup.py 文件一样,只不过需要自己手动创建,区别在于你可以任意命名该文件(如 woshinibaba.py)
四、在 setup.py(woshinibaba.py) 文件中写入需要的依赖
文件中基本格式为
# -*- coding: utf-8 -*- from distutils.core import setup import py2exe setup( # console和windows分别代表控制台和图形界面,按需求选择 #console = [{"script" : 'comtrade.py'}], windows = [{"script":"comtrade.py", "icon_resources": [(1, "logo.ico")]} ], name = 'comtrade',# 生成的exe文件名 version = '1.0', options={}, # 括号内填入的为项目所需的依赖库和会造成报错的文件 data_files={})# 括号内输入的为项目所需的依赖文件
# version ,description,name不是必须要写的。
其他参数:
- dist_dir 打包生成的文件放在 dist 下,可设置存放目录(一般没有特殊要求,可以不需修改。可使用相对路径)
- Compressed 默认为 0,1 为指定压缩文件(library.zip)的行为;0 为不压缩。
- Zipfiles 配置共享压缩文件的生成目录和文件名,默认是在目录 dist 下生成一个 “library.zip” 文件,打包了 .exe 文件运行需要的 .pyd 和 .dll 文件(不包含配置文件等)。
- Optimize 打包优化,合法值是字符串('','O','OO')或者整型数字 (0, 1, or 2)。
- 为 0 时:不进行优化,压缩包大小较大,打包的编译文件为 .pyc;
- 为 1 时:进行少量优化,压缩包大小略小,打包的编译文件为 .pyo;
- 为 2 时:优化级别最高,压缩包大小也明显变小,打包的编译文件为 .pyo。
- Bundle_files 打包绑定,64位不支持此属性。
- 为 0 时:pyd 和 dll 文件不会被打包到 exe 文件中;
- 为 1 时:pyd 和 dll 文件会被打包到 exe 文件中,且不能从文件系统中加载 python 模块;
- 为 2 时:pyd 和 dll 文件会被打包到 exe 文件中,但是可以从文件系统中加载 python 模块。
- 注:
- .py:编写的源文件。
- .pyc:编译过的二进制代码文件。如果导入一个模块,python 将创建一个 *.pyc 文件,文件内为二进制码,这样可以在再次导入时更容易(更快)。
- .pyo:当优化等级 (-O) 开启时生成的 *.pyc 文件。
- .pyd:相当于一个 windows dll 文件。实际上 .pyd 文件就是 dll 文件,只是略有不同。
- Date_files 文件可执行文件所需数据。在 python27 中,需要的 MSVCP90.dll 不能单独发布,必须确保 py2exe 复制所有的三个 dll 文件和 manifest 文件到工程目录 dist 下,并且放在一个名为 'Microsoft.VC90.CRT' 的子目录下。
- 参考做法为:
from glob import glob data_files = [ ("Microsoft.VC90.CRT", glob(r'C:\Program Files\Microsoft Visual Studio freeze_support9.0\VC\redist\x86\Microsoft.VC90.CRT\*.*')) ]
- 参考做法为:
- ascii
- 为 0 时:不包含编码和解码器;
- 为 1 时则反之。
- 假设出现 QPixmap::scaled: Pixmap is a null pixmap 问题,这是由于 PyQt 和 qt 都是默认的 png 格式的图片,打包后,会找不到 jpg 格式的图片,所以在打包过程中需要把 PyQt4 文件中的imageformats 文件夹下的 dll 文件导入。这是 jpg 格式的图片需要的插件。
- 类标识符无属性,产生的CLSID无属性。
typelibs |
列表:需要包含的gen_py产生的typelibs |
- 多进程打包遇到的程序不正常执行问题,需要在多进程之前调用 freeze_support() 函数。经试验,最好在函数开始执行的时候,首先调用此函数。
具体例子:
# -*- coding: utf-8 -*- # 必须写的倒入模块 from distutils.core import setup import py2exe # 可以不用写的部分 """ #We need to import the glob module to search for all files. import glob import sys #this allows to run it with a simple double click. sys.argv.append('py2exe') """ # 项目中需要用到的第三方库写入includes所对应的列表中 # 项目中用不到的会造成报错的文件放在excludes所对应的列表中,若报错的是dll文件,放入dll_excludes所对应的列表中 # 下面是示例: opts= { 'py2exe':{ "includes" : [ "sip", "matplotlib.backends", "matplotlib.backends.backend_tkagg", "matplotlib.figure","numpy", "matplotlib.pyplot", "pylab", "six", "matplotlib.backend_bases", 'scipy.special._ufuncs_cxx', "scipy.integrate","scipy.integrate.quadpack","scipy.sparse.csgraph._validation"], "excludes" : ['_gtkagg', '_tkagg', '_agg2', '_cairo', '_cocoaagg', '_fltkagg','_gtk', '_gtkcairo'], "dll_excludes":['libgdk-win32-2.0-0.dll','libgobject-2.0-0.dll',"MSVCP90.dll",] } } #项目中需要用到的外部文件依赖放入列表中,格式为[(),(),()] # 元祖中第一个元素是打包时要创建的文件夹名,如果要放在exe文件的同目录下就用"",第二个元素是该依赖文件的路径 data_files= [(r'mpl-data',glob.glob(r'C:\Anaconda3\Lib\site-packages\matplotlib\mpl-data\*.*')), #Because matplotlibrc does not have an extension, glob does not findit (at least I think that's why) #So add it manually here: (r'mpl-data',[r'C:\Anaconda3\Lib\site-packages\matplotlib\mpl-data\matplotlibrc']), (r'mpl-data\images',glob.glob(r'C:\Anaconda3\Lib\site-packages\matplotlib\mpl-data\images\*.*')), (r'mpl-data\stylelib',glob.glob(r'C:\Anaconda3\Lib\site-packages\matplotlib\mpl-data\stylelib\*.*')), (r'mpl-data\fonts',glob.glob(r'C:\Anaconda3\Lib\site-packages\matplotlib\mpl-data\fonts\*.*')), ("",[r"C:\Anaconda3\Lib\site-packages\PyQt5\libEGL.dll"]), ("platforms",[r"C:\Anaconda3\Lib\site-packages\PyQt5\plugins\platforms\qwindows.dll"])] # 将上述参数传入setup中,console和windows分别代表控制台和图形界面,按需求选择 setup( #console = [{"script" : 'comtrade.py'}], windows = [{"script":"comtrade.py", "icon_resources": [(1, "logo.ico")]} ], name = 'comtrade', version = '1.0', options=opts, data_files=data_files)
五、生成exe
python setup.py app2exe
执行完毕后会生成build和dist文件夹,启动文件在dist文件夹下
py2exe报错解决
1. 执行打包命令时报错 Missing run-py3.5-win-amd64.exe
- 原因:py2exe 最高只支持到 python3.4,如果你用的 3.5 或更高的版本就会出现这个问题
- 解决方法:创建个虚拟环境安装 python3.4,然后 pip install 所有项目需要的第三方库后重新进行一边打包操作
2. 执行打包命令时报错 indexError: tuple index out of range
- 原因:py2exe 最高只支持到 python3.4,如果用更高的版本就会出现这个问题
- 解决方法:创建个虚拟环境安装 python3.4,然后 pip install 所有项目需要的第三方库后重新进行一边打包操作
3. 执行打包操作时报错 (忘了具体报错信息,意思时递归深度超过最大限制)
- 原因:py2ex e最高只支持到 python3.4,如果你用的更高的版本就会出现这个问题
- 解决方法:创建个虚拟环境安装 python3.4,然后 pip install 所有项目需要的第三方库后重新进行一边打包操作
4. 打包 ok,但双击可执行文件时报错 Failed to execute script xxx
- 原因:去 log 文件中查看,会发现报错信息为 no module named xxx,意思为项目中缺少 xxx 模块
- 解决方法:pip install ,如果你确定你已经安装了该模块,那就在 setup.py(woshinibaba.py)文件最上面 import 该模块
5. 打包 ok,但双击可执行文件时弹窗报错 This application failed to start because it could not find or load the Qt platform plugin "windows".
注:这是我遇到的一个最大的问题,问题原因和 PyQt5 有关目前尚未找到解决方案,然后选用了 pyinstaller
- 疑似原因一:python3.4 不支持 PyQt5
- 本人理解:python3.4 环境下用 pip install PyQt5,报错说找不到该模块,但是可以运行 pip install pyqt,而 pyqt 指得是 PyQt4,两者是不一样的。在 pycharm 中将其升级为 PyQt5,惊奇的发现我的python 环境变成 python3.7了?!在升级 pyqt 的同时将我的 python 都升级了?但是 py2exe 不支持 python3.7 啊,WTF?!最后本人不了了之选择了pyinstaller
- 疑似原因二:没有将 PyQt5 写入环境变量
- 网上提供的解决方法一:将 QT 的 bin 目录下的 \platforms\qwindows.dll 拷贝至 exe 所在目录,注意保留 \platforms 子目录
- 网上提供的解决方法二:在 data_files 参数中加入两个元祖,元祖中写入(该方法与上面的方法一个作用,他会在你执行打包命令时自动将将 QT 的 bin 目录下的 \platforms\qwindows.dll 拷贝至 exe 所在目录)
# 注:路径为你的python的PyQt5的路径 data_files=[("", [r"F:\Python\python3\Lib\site-packages\PyQt5\libEGL.dll"]), ("platforms", [r"F:\Python\python3\Lib\site-packages\PyQt5\plugins\platforms\qwindows.dll"])]
- 网上提供的解决方法三:改变系统变量(变量值为你的 python 所在的目录下的 PyQt5 的目录)
pyinstaller
首先要声明,pyinstaller 在高版本的 python 环境下可能会出现不支持的情况,我在打包的时候只支持到 python3.5,不清楚目前支持到哪个 python 版本。如果你的 python 已是3.5以上的版本,建议创建一个虚拟环境后安装 python3.5,再自行安装上程序所依赖的库比如 requests 等等,在新环境中进行打包。
注:pyinstaller 方法已在 win10、win8 和 Mac环境下测试无误,但打包程序本身会因为你的程序的不同而需要有些许改动,文章末尾会有一些我遇到过的报错的解决方法,出现问题可自行Google
一、安装pyinstaller
pip install pyinstaller
二、切换到工作目录
cd xxxxxxxxxxx
三、打包命令
与上面两个不同的是,pyinstaller 不需要自己写 setup.py 文件,只需要在工作目录中输入打包命令即可。最后会生成 build 和 dist 文件夹,启动文件在 dist 文件夹下。
命令格式:
pyinstaller [项目启动文件]
其他参数(按需求选择):
- -F 表示在 dist 文件夹下只生成单个可执行文件(内部包含所有依赖),不加默认会在 dist 生成一大堆依赖文件+可执行文件。
- -D 与 -F 相反用法
- -W 表示去掉控制台窗口,如果你的程序是有界面的,可以不写这个参数。但是测试情况下建议先加上这个参数,因为如果打包不成功,运行时报错信息会在控制台上输出,没有控制台就看不到报错信息。
- -c 表示去掉窗框,使用控制台
- -p 表示自己定义需要加载的类路径,项目中包含多个自建模块的时候需要加上 -p aaa.py -p bbb.py -p ccc.py
- -i 表示可执行文件的图标,后面跟图标的路径
- --hidden-import 后面跟模块名如 queue,用于告诉打包程序某个模块我用不着你不用打包进去
打包完毕后在 dist 文件夹下双击项目启动文件就可以了
pyinstaller报错解决
1.执行打包命令时报错 IndexError: tuple index out of range
- 原因:官网目前的版本是 3.2.1 只支持到 python3.5 ,高版本的 python 尚不支持,
- 解决方法:网上有大神提供了完善版的代码——官网源码里有 https://github.com/pyinstaller/pyinstaller 替换你 python 目录下的 \Lib\site-packages\PyInstaller 即可 这样就支持python3.6了 不过是开发版,可能还不完善。
作者建议最好还是用虚拟环境下的 python3.5 进行打包。
2.执行打包命令时报错 ImportError: No module named 'queue'
- 原因:尚不清楚
- 解决方法:如果该模块你用不到,可以在执行打包命令时用 --hidden-import 不打包进去。如果程序中需要该模块,在主文件最上面写上 improt queue
3.打包命令执行成功,但双击可执行程序弹出报错窗口failed to excute script xxx
- 原因:打包时内部缺少了某个依赖,这时需要看看控制台打印了什么报错信息,打包时加了 -w 参数的请再打包一次记得去掉 -w
- 现象:基本都是在控制台上发现报错 No module named 'xxxxx',如 No module named 'queue' 或者 ModuleNotFoundError: No module named 'PyQt5.sip'
- 解决方法:同2,如果该模块你用不到,可以在执行打包命令时用 --hidden-import 不打包进去。如果程序中需要该模块,在主文件最上面写上 improt xxxxx。如 import queue 或 import PyQt5.sip