python程序打包攻略
最近在做一门迷之课程的编程作业(老师这门课要上多久都不知道,简直是个傻子,总之非常一言难尽),最后要发给老师,所以必须打包。程序写了两天,打包,,,包了3天……
尝试了很多方法,最后终于找到一套还说的过去的方案。
可选的方法
- 使用
cx_Freeze
库。我以前用过这种方法,效果不错,但是这次总是不行。首先是cx_Freeze
似乎不能打包NumPy
以及任何依赖于它的库;第二是在我的电脑上,cx_Freeze
出错退出时没有任何错误信息,我甚至不知道发生了什么,这浪费了我很多时间。我已经提交了issue,在作者修复之前我想我是不会去碰了。其实cx_Freeze
能通过setup.py
进行构建,并且打包时能自动识别用到的库,能修复问题还是不错的,用户文档在这里。 - 使用
PyInstaller
库。这个我之前也用过,似乎会无脑把系统中的python库全都打包进去,导致输出文件体积颇大。但是相比前者而言,至少能用。 - 歪门邪道,不进行真正的打包,只是把python本体和程序压缩一下了事。这样不能像
.exe
一样隐藏代码,但是打包后体积一般比PyInstaller
输出的小。
在多方测试之后,有两种方案可行。
推荐方案:虚拟环境下的PyInstaller
PyInstaller
会把系统中的python库全都打包,怎么办?那就把多余的包删掉那就创造一个没有多余的包的环境。这也有三种方法:使用conda
创建,使用virtualenv
创建,和直接下载一个嵌入版python创建。我测试下来,最稳的是用virtualenv
。
使用virtualenv创建虚拟环境
安装方法很简单,pip install virtualenv
。
然后在程序目录中(路径中最好不要有中文和特殊字符),使用
1 | virtualenv --no-site-packages name |
这将会在当前目录下创建一个名为name
的文件夹,里面就是一个不含额外库的python(当然,已经装好了pip
,setuptools
和wheel
)。然后要启动这个虚拟环境,注意,不要在PowerShell下操作,最好使用cmd。各种有用的命令行程序都在\name\Scripts
文件夹中(注意在cmd中,使用正斜线作为路径分隔很容易引发错误),包括启动命令activate
以及结束命令deactivate
,还有pip
。
1 | \name\Scripts\activate |
启动成功的标志是cmd命令提示符之前出现(name)
的标志。按照廖雪峰的教程,启动之后,\name\Scripts
会临时进入系统路径,此时在任何位置调用pip
都是这里的——然而如果建立虚拟环境时路径中有中文和特殊字符,就不行了,只能每次都\name\Scripts\pip
凑活一下。
之后在这个虚拟环境里,使用pip
安装上程序需要的库,最后pip install pyinstaller
。
PyInstaller的基本使用方法
完整用户手册在这里。
最简单的用法,就是直接在命令行中指定选项,基本就是这样
1 | pyinstaller [options] my_script.py |
运行之后,会在运行这条命令的路径下生成构建选项文件my_script.spec
,以及两个文件夹,build
保存构建中用到的文件,dist
保存构建结果。
PyInstaller的进阶使用方法
通过编辑.spec
文件可以进行更详细的设定,例如当程序中用到外部文件时,可以用--add-data
选项,但是直接在.spec
文件中指定来的更明白。
使用如下命令,可以仅仅生成.spec
文件,而不进行后续步骤:
1 | pyi-makespec [options] name.py |
如果要添加外部文件,就把.spec
文件中的datas=[]
改成[('file1','path1'),('file2','path2')]
的形式,path
也是相对于运行这条命令的路径的(但是为什么不写成('\path\file','.')
呢,这样更明白)。
修改好后,使用pyinstaller my_script.spec
运行即可。
PyInstaller 的其他问题
有时使用时会出现一些关于encode的问题(错误报告忘了记录下来了Orz),可以在\Lib\site-packages\PyInstaller
找到compet.py
,在389行,加上一个参数error = 'ignore'
。(解决问题的方法,就是直接忽视它)
备选方案:创造一个合适的python环境,然后压缩起来
其实还是用PyInstaller较好,基本不会有DLL问题,但是有时候它的输出结果实在太大,或者有无法解决的bug,那么可以考虑这种方法。
搭建嵌入式python环境
先去官网下载一个嵌入版压缩包(考虑到程序可能要在某些古老电脑运行,可以下载32位版,即Windows x86 embeddable zip file),建议使用python3.5.4,因为更低版本不支持某些新功能,更高版本再下一步会出bug,下载网址是https://www.python.org/downloads/release/python-354/。
解压之后就可以得到python的“核”,它很精简,甚至不包含pip
三件套。把这个脚本保存为get-pip.py
,用“核”中包含的python.exe
运行它,就能安装上三件套了。
之后的操作,就是用Scripts
目录下的pip
安装上各种需要的包。有时候会出问题,可以先把.whl
文件下载到本地再安装,这个网站提供的比较全:https://www.lfd.uci.edu/~gohlke/pythonlibs/
用批处理文件打造程序入口
这种方法无法把代码冻结起来(全开源……)。显然,让用户直接去点击.py
文件运行是不妥当的,可以把主程序藏到\lib
文件夹里,写一个批处理文件run.bat
什么的当作入口:
1 | python3.5.4\python lib\my_script.py |
打包
然后把文件夹压缩一下就好了。
但是如果你还是觉得这压缩包太大(仅仅PySide2
一个库就60多兆……),可以试试让用户自己安装。就包含一个python的核,外加自己的程序和get-pip.py
,然后写一个install.bat
,包含安装pip
和各种库的过程,别忘了安装完成后得给点提示,比如:
1 | @echo Install Successful! |
但是实际应用中发现这方法不太行,因为下载库的时候经常卡到出超时错误……
其他问题
- 如果用到Qt相关的库的话,可能需要手动指定一下使用的库的路径,例如使用
PySide2
:
1
2
3
4import os
dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, 'plugins', 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path
- 如果用到
matplotlib
,当然还是要解决一下字体问题,如果使用黑科技install.bat
,可以先在lib
里存一个改过的matplotlibrc
,然后在安装过程中
1
copy /y lib\matplotlibrc python3\Lib\site-packages\matplotlib\mpl-data
要用到的字体也如法炮制。