1.概述
需求:当我们用python写好一个单机版小程序,想发布在网上或给朋友使用时,我们希望能将程序和python环境一起打包成一个可执行文件双击运行即可,网友下载后直接运行不需要再安装python环境和相关依赖。
pyinstaller是一个能将Python程序转换成单个可执行文件的程序, 操作系统支持Windows, Linux, Mac OS X, Solaris和AIX。并且很多包都支持开箱即用,不依赖环境。
2.打包
此处我们以win10+python3.5+virtualenv环境为例演示打包过程
先准备一个写好的python小程序,我们用Flask将结巴分词的cut分词展示在网页上源码下载
在一个空目录中创建一个virtualenv的虚拟环境,并安装应用的相关依赖:
D:\workspace\Python\test\test-pyinstaller>virtualenv env
Using base prefix 'c:\\soft\\python\\python3'
New python executable in D:\workspace\Python\test\test-pyinstaller\env\Scripts\python.exe
Installing setuptools, pip, wheel...done.
D:\workspace\Python\test\test-pyinstaller>env\Scripts\activate
(env) D:\workspace\Python\test\test-pyinstaller>pip install Flask
Collecting Flask
Using cached Flask-0.12.2-py2.py3-none-any.whl
Collecting itsdangerous>=0.21 (from Flask)
Collecting Werkzeug>=0.7 (from Flask)
Using cached Werkzeug-0.12.2-py2.py3-none-any.whl
Collecting click>=2.0 (from Flask)
Using cached click-6.7-py2.py3-none-any.whl
Collecting Jinja2>=2.4 (from Flask)
Using cached Jinja2-2.9.6-py2.py3-none-any.whl
Collecting MarkupSafe>=0.23 (from Jinja2>=2.4->Flask)
Installing collected packages: itsdangerous, Werkzeug, click, MarkupSafe, Jinja2, Flask
Successfully installed Flask-0.12.2 Jinja2-2.9.6 MarkupSafe-1.0 Werkzeug-0.12.2 click-6.7 itsdangerous-0.24
(env) D:\workspace\Python\test\test-pyinstaller>pip install jieba
Collecting jieba
Installing collected packages: jieba
Successfully installed jieba-0.38
(env) D:\workspace\Python\test\test-pyinstaller>
在虚拟环境中安装打包需要的依赖:
在windows环境中会依赖win32api,所以需要下载pywin32,在此处下载对应python版本的pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/,下载好后用easy_install安装;安装完成后再安装pyinstaller,此处从git上安装最新版本的pyinstaller
(env) D:\workspace\Python\test\test-pyinstaller>easy_install pywin32-221.win-amd64-py3.5.exe
(env) D:\workspace\Python\test\test-pyinstaller>pip install git+https://github.com/pyinstaller/pyinstaller
环境准备完毕,我们项目中的目录结构如下:
pyinstaller打包命令如下:
pyinstaller [-F] [-w] [-p 模块] [-i logo.icon] 程序入口文件路径
参数说明:
-F 表示生成单个可执行文件
-w 表示去掉控制台窗口,这在GUI界面时非常有用。不过如果是命令行程序的话那就把这个选项删除吧!
-p 表示你自己自定义需要加载的类路径,一般情况下用不到
-i 表示可执行文件的图标
在程序目录执行如下命令进行打包:
(env) D:\workspace\Python\test\test-pyinstaller>pyinstaller app.py
打包完成后目录结构如下:
在dist目录中会包含一个与可执行文件同名的目录,目录里面会有一个与可执行文件同名的.exe文件,这个文件及为可执行文件。
若我们的程序依赖一些静态资源我们需要将静态资源拷贝到可执行文件所在目录,我们上面的程序需要将模板文件与结巴分词的词典拷贝进去。
然后双击app.exe启动flask应用,在网页访问查看效果:
3.踩过的坑
安装pyinstaller时直接使用pip install pyinstaller,打包时会报一些错误,所以直接安装git上的最新版
如我们程序中使用了多进程multiprocessing,我们需要创建一个模块内容如下:
# Module multiprocessing is organized differently in Python 3.4+
try:
# Python 3.4+
if sys.platform.startswith('win'):
import multiprocessing.popen_spawn_win32 as forking
else:
import multiprocessing.popen_fork as forking
except ImportError:
import multiprocessing.forking as forking
if sys.platform.startswith('win'):
# First define a modified version of Popen.
class _Popen(forking.Popen):
def __init__(self, *args, **kw):
if hasattr(sys, 'frozen'):
# We have to set original _MEIPASS2 value from sys._MEIPASS
# to get --onefile mode working.
os.putenv('_MEIPASS2', sys._MEIPASS)
try:
super(_Popen, self).__init__(*args, **kw)
finally:
if hasattr(sys, 'frozen'):
# On some platforms (e.g. AIX) 'os.unsetenv()' is not
# available. In those cases we cannot delete the variable
# but only set it to the empty string. The bootloader
# can handle this case.
if hasattr(os, 'unsetenv'):
os.unsetenv('_MEIPASS2')
else:
os.putenv('_MEIPASS2', '')
# Second override 'Popen' class with our modified version.
forking.Popen = _Popen
然后在我们的main方法的最前面调用如下代码:
import multiprocessing
multiprocessing.freeze_support()