文章目录
  1. 1. 可选的方法
  2. 2. 推荐方案:虚拟环境下的PyInstaller
    1. 2.1. 使用virtualenv创建虚拟环境
    2. 2.2. PyInstaller的基本使用方法
    3. 2.3. PyInstaller的进阶使用方法
    4. 2.4. PyInstaller 的其他问题
  3. 3. 备选方案:创造一个合适的python环境,然后压缩起来
    1. 3.1. 搭建嵌入式python环境
    2. 3.2. 用批处理文件打造程序入口
    3. 3.3. 打包
  4. 4. 其他问题

最近在做一门迷之课程的编程作业(老师这门课要上多久都不知道,简直是个傻子,总之非常一言难尽),最后要发给老师,所以必须打包。程序写了两天,打包,,,包了3天……

尝试了很多方法,最后终于找到一套还说的过去的方案。

可选的方法

  1. 使用cx_Freeze库。我以前用过这种方法,效果不错,但是这次总是不行。首先是cx_Freeze似乎不能打包NumPy以及任何依赖于它的库;第二是在我的电脑上,cx_Freeze出错退出时没有任何错误信息,我甚至不知道发生了什么,这浪费了我很多时间。我已经提交了issue,在作者修复之前我想我是不会去碰了。其实cx_Freeze能通过setup.py进行构建,并且打包时能自动识别用到的库,能修复问题还是不错的,用户文档在这里
  2. 使用PyInstaller库。这个我之前也用过,似乎会无脑把系统中的python库全都打包进去,导致输出文件体积颇大。但是相比前者而言,至少能用。
  3. 歪门邪道,不进行真正的打包,只是把python本体和程序压缩一下了事。这样不能像.exe一样隐藏代码,但是打包后体积一般比PyInstaller输出的小。

在多方测试之后,有两种方案可行。

推荐方案:虚拟环境下的PyInstaller

PyInstaller会把系统中的python库全都打包,怎么办?那就把多余的包删掉那就创造一个没有多余的包的环境。这也有三种方法:使用conda创建,使用virtualenv创建,和直接下载一个嵌入版python创建。我测试下来,最稳的是用virtualenv

使用virtualenv创建虚拟环境

安装方法很简单,pip install virtualenv

然后在程序目录中(路径中最好不要有中文和特殊字符),使用

1
virtualenv --no-site-packages name

这将会在当前目录下创建一个名为name的文件夹,里面就是一个不含额外库的python(当然,已经装好了pip,setuptoolswheel)。然后要启动这个虚拟环境,注意,不要在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
2
3
4
5
6
7
8
9
10
pyinstaller [options] my_script.py
常用 [options]:
-h 显示help并退出
-D 生成一个文件夹,其中包含一个exe(默认)
-F 生成单个exe文件
-w 生成一个无命令行界面的程序
-i file.ico 指定图标
--add-data SRC;DEST 在程序中用到的其他(非二进制)文件,不建议用(见后文)
--hidden-import MODULENAME 在程序中隐式导入的库,可多次使用
--exclude-module MODULENAME 不希望导入的库,可多次使用

运行之后,会在运行这条命令的路径下生成构建选项文件my_script.spec,以及两个文件夹,build保存构建中用到的文件,dist保存构建结果。

PyInstaller的进阶使用方法

通过编辑.spec文件可以进行更详细的设定,例如当程序中用到外部文件时,可以用--add-data选项,但是直接在.spec文件中指定来的更明白。

使用如下命令,可以仅仅生成.spec文件,而不进行后续步骤:

1
2
pyi-makespec [options] name.py
[options] 与之前类似

如果要添加外部文件,就把.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
2
@echo Install Successful!
pause

但是实际应用中发现这方法不太行,因为下载库的时候经常卡到出超时错误……

其他问题

  • 如果用到Qt相关的库的话,可能需要手动指定一下使用的库的路径,例如使用PySide2

1
2
3
4
import 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

要用到的字体也如法炮制。

文章目录
  1. 1. 可选的方法
  2. 2. 推荐方案:虚拟环境下的PyInstaller
    1. 2.1. 使用virtualenv创建虚拟环境
    2. 2.2. PyInstaller的基本使用方法
    3. 2.3. PyInstaller的进阶使用方法
    4. 2.4. PyInstaller 的其他问题
  3. 3. 备选方案:创造一个合适的python环境,然后压缩起来
    1. 3.1. 搭建嵌入式python环境
    2. 3.2. 用批处理文件打造程序入口
    3. 3.3. 打包
  4. 4. 其他问题