目录
打包前置知识
exe%E5%8F%AF%E6%89%A7%E8%A1%8C%E6%96%87%E4%BB%B6%EF%BC%9F-toc" style="margin-left:80px;">一、什么是exe可执行文件?
exe%20%E5%8F%AF%E6%89%A7%E8%A1%8C%E6%96%87%E4%BB%B6%EF%BC%9F-toc" style="margin-left:80px;">二、为什么要将 Python 程序打包为 exe 可执行文件?
三、为什么 Python 程序不能直接运行呢?
四、我们用什么来打包 Python 文件呢?
五、打包有哪几种分类呢?
打包的方法
一般的打包
1、打开 Anaconda Prompt
Pyinstaller%20%E6%A8%A1%E5%9D%97-toc" style="margin-left:80px;">2、下载并安装 Pyinstaller 模块
3、切换命令行的路径
4、打包 Python 文件
5、打包生成文件的位置
虚拟环境下的打包
0、先介绍几个 conda 命令
1、创建虚拟环境
2、安装需要的第三方包
exe%E5%A4%A7%E5%B0%8F%EF%BC%88%E9%9D%9E%E5%BF%85%E8%A6%81%E6%AD%A5%E9%AA%A4%EF%BC%89-toc" style="margin-left:80px;">3、追求极致的exe大小(非必要步骤)
多 Python 文件的打包
1、生成spec文件
2、编辑spec文件
3、以spec文件进行打包
包含资源文件的打包
0、一点吐槽
1、编辑spec文件
2、修改文件打开函数
打包实战
打包方式的选择
打包全过程
第一步:启动 Anaconda Prompt,切换至目标文件夹路径位置
Pyinstaller%20%E7%9A%84%E7%8E%AF%E5%A2%83%EF%BC%89-toc" style="margin-left:80px;">第二步:启动虚拟环境(我的是一个纯净的、第三方包只有 Pyinstaller 的环境)
Pyinstaller%20%E5%B7%B2%E7%BB%8F%E5%AE%89%E8%A3%85%E5%A5%BD%E4%BA%86%EF%BC%89-toc" style="margin-left:80px;">第三步:生成 spec 文件(我的 Pyinstaller 已经安装好了)
第四步:引入 _.py 模块(我的程序用到了大量 open 函数且涉及多文件)
第五步:编辑 spec 文件
第六步:打包项目(注意这里的对象是 spec 文件)
打包前置知识
exe%E5%8F%AF%E6%89%A7%E8%A1%8C%E6%96%87%E4%BB%B6%EF%BC%9F">一、什么是exe可执行文件?
exe 文件英文全名是 executable file,翻译为可执行文件(但它不等于可执行文件),可执行文件包含两种,文件扩展名为 .exe 的是其中的一种。正确的 exe 文件可以在 Windows 平台上直接双击运行!我们通常用的各种软件都是通过快捷方式打开的,而这个快捷方式的目标地址就是这个软件的一个 exe 文件。
exe%20%E5%8F%AF%E6%89%A7%E8%A1%8C%E6%96%87%E4%BB%B6%EF%BC%9F">二、为什么要将 Python 程序打包为 exe 可执行文件?
众所周知,Python 程序的运行必须要有 Python 的环境,但是程序编出来是用的,如果是给别人用,而他/她的电脑上又没有 Python 程序运行的环境怎么办呢?总不能让他/她去安装一个吧?这时我们就要将 Python 程序打包为 exe 文件。这样,在 Windows 平台下,就可以直接运行该程序,不论有没有 Python 环境。
三、为什么 Python 程序不能直接运行呢?
Python 是解释性语言,它与 C 或者 C++ 等编译型语言不同,C 或者 C++ 都是要编译再运行的,(编译产生的最终文件就是 exe 文件),Python 本质上只是对一段文本进行解释,类似于浏览器解析 html 文档,是不会产生任何 exe 文件的。
四、我们用什么来打包 Python 文件呢?
一般我们都用 Python 的 Pyinstaller 模块进行打包,也有其他的打包模块,不过相比之下,Pyinstaller 的使用者最多,用起来也很简单,因此本文章就以 Pyinstaller 模块来打包 Python 程序。
五、打包有哪几种分类呢?
根据需要,下面的方法大家可以任选一种进行打包(我一般用第 3 个),不过新手的话建议全部都看一下哦。
① 一般的打包
步骤最少,操作最简单,但是打包时间久,效果不理想(打包后文件太大,一般 100MB 以上)
② 虚拟环境下的打包
步骤稍多,操作略微复杂,但是打包快,效果好(打包后文件不大,一般 10MB 以内)
③ 多 Python 文件的打包
步骤更多,操作更复杂,但是可以将多个 Python 文件都打包进去
④ 包含资源文件的打包
步骤极为繁琐,操作非常复杂,但是可以把所有的文件都包含进去
打包的方法
一般的打包
一般的打包方式,最简单,但是打包的成品有些许臃肿,不是特别推荐。
1、打开 Anaconda Prompt
如果你安装了 Anaconda 的 Python 集成环境的话,在菜单页面的所有应用里面可以看到 Anaconda 以及 Anaconda Prompt。
点进去就可以看见如下的界面:
Pyinstaller%20%E6%A8%A1%E5%9D%97">2、下载并安装 Pyinstaller 模块
这个用 pip 模块直接下载就行,直接就下载在本次需要打包的 Python 环境下(base 环境)
python">pip install Pyinstaller
当然了,已经安装过 Pyinstaller 模块的可以跳过这一步。
如果出现什么疑难杂症,大概率是权限导致的问题,按照下面的方法重新打开 Anaconda Prompt 就好了。
此时,Anaconda Prompt 的显示文字会变成如下这样:
然后再 pip 就行,这样应该就没有什么问题了。
3、切换命令行的路径
因为你要打包的文件在对应的文件夹里面,而 Pyinsatller 一开始是不知道要打包的文件在哪里的,所以要直接切换命令行的路径到目标文件夹路径,使得后面的步骤中,Pyinstaller 可以找到对应的文件。
python">cd 文件夹路径
这里我的打包文件夹放在了桌面上,文件夹名为 test,要打包的 Python 文件在 test 文件夹内,名为 Python.py 。于是我的文件夹路径为 C:\Users\小康\Desktop\test(一定要是绝对路径)。
然后回车就可以看到下面这样的就说明成功了。
4、打包 Python 文件
输入如下格式的命令即可
python">Pyinsatller -option1 -option2 -... 要打包的文件
参数选项比较多,这里我列一个表:
参数选项 | 描述 |
-F, -onefile | 只生成一个单个文件(只有一个 exe 文件) |
-D, -onedir | 打包多个文件,在dist中生成很多依赖文件,适合以框架形式编写工具代码,这样代码易于维护 |
-K, –tk | 在部署时包含 TCL/TK |
-a, -ascii | 不包含编码 在支持 Unicode 的 Python 版本上默认包含所有的编码 |
-d, -debug | 产生 debug 版本的可执行文件 |
-w, -windowed, -noconsole | 使用 Windows 子系统执行 当程序启动的时候不会打开命令行(只对 Windows 有效) |
-c, -nowindowed, -console | 使用控制台子系统执行(默认)(只对 Windows 有效) pyinstaller -c xxxx.py pyinstaller xxxx.py --console |
-s, -strip | 可执行文件和共享库将 run through strip 注意 Cygwin 的 strip 往往使普通的 win32 Dll 无法使用 |
-X, -upx | 如果有 UPX 安装(执行 Configure.py 时检测),会压缩执行文件( Windows 系统中的 DLL 也会) |
-o DIR, -out=DIR | 指定 spec 文件的生成目录,如果没有指定,而且当前目录是 PyInstaller 的根目录,会自动创建一个用于输出( spec 和生成的可执行文件)的目录 如果没有指定,而当前目录不是 Pyinstaller 的根目录,则会输出到当前的目录下 |
-p DIR, -path=DIR | 设置导入路径(和使用 PYTHONPATH 效果相似) 可以用路径分割符( Windows 使用分号,Linux 使用冒号)分割,指定多个目录 也可以使用多个 -p 参数来设置多个导入路径,让 pyinstaller 自己去找程序需要的资源 |
-i -icon=<FILE.ICO> | 将 file.ico 添加为可执行文件的资源(只对 Windows 系统有效),改变程序的图标 |
-i -icon=<FILE.EXE,N> | 将 file.exe 的第 n 个图标添加为可执行文件的资源(只对 Windows 系统有效) |
-v FILE, -version=FILE | 将 verfile 作为可执行文件的版本资源(只对 Windows 系统有效) |
-n NAME, -name=NAME | 可选的项目(产生的 spec 的)名字 如果省略,第一个脚本的主文件名将作为 spec 的名字 |
这里简单地举几个例子,让大家明白这个参数怎么写。
python"># 这一般是用来打包界面化的程序的,如用tkinter、Pyqt5等制作的程序。
# -w 的意思就是exe运行的时候不弹出那个命令行(黑窗口)
Pyinstaller -F -w somefile.py
# 这一般用来添加exe的图标
Pyinstaller -F -i someicon.ico somefile.py
然后回车它就会自动打包了。说明一下,一般我们都只会选择其中的几个参数选项,如 -F 和 -w,根据需要,我们还会选择其他的一些参数。当出现如下的文字(主要是最后一行文字)时就代表打包成功了!
5、打包生成文件的位置
让我们回到最初切换的文件夹里,我们可以看到,多了下面三个文件(build 文件夹、dist 文件夹和 spec 文件):
我们想要的 exe 文件就在新生成的 dist 文件夹里面。此时的 exe 文件有可能还运行不了,因为它可能涉及到一些资源文件或者其他的 Python 文件。将它们放到同一文件夹下即可正确运行。
这里说明一下,打包完之后,spec 文件和 build 文件夹就没用了,可以删除了。
这里一般的打包方式产生的 exe 文件都比较大,这是因为 Pyinstaller 打包的时候会把你环境中的库和模块全部打包进去,这就会使一些你根本用不着的库和模块也被打包进去了!而且这些库被打包之后不仅会使 exe 文件变大,还会使其运行变卡变慢、变得十分臃肿。因此,不建议这样的打包方式。十分地建议大家用第二种方式进行打包 —— 虚拟环境下的打包。
虚拟环境下的打包
所谓的虚拟环境,就是我们自己创建一个小型的 Python 环境,也可以这样理解,自己创一个新的、纯净的、没有奇奇怪怪的第三方库和模块的 Python 环境。这个环境你也是可以用来编写 Python 程序的,但这里我们是要来打包 exe 的,这就要求它里面的库和模块尽可能的少。
0、先介绍几个 conda 命令
① 导出虚拟环境的列表
python">conda env list
② 导出当前环境的包
python">conda list
③ 启动/切换至名为name的Python环境
python">conda activate name
默认为base环境(名为base)
④ 退出虚拟环境
python">conda deactivate
退出后会自动回到base环境
⑤ 创建新的、名为name的、Python 版本为3.x的虚拟环境
python">conda create -n name python==3.x
1、创建虚拟环境
和一般的打包方式一样,打开 Anaconda Prompt,然后输入如下格式的命令:
python">conda create -n env_1 python==3.10.8
这里它会停下来询问你新的环境是否要安装这些包,这些包大部分都是一些必须的包,直接输入 y 或者直接回车即可。
2、安装需要的第三方包
这样,我们的虚拟环境就弄好了!但是!这并不代表着它就是符合你程序运行的环境,如果你的 Python 程序还用到了一些其他的第三方库,那么就一定要把这些库给添加进这个虚拟环境,添加方式就是直接在当前环境下用 pip。
下载库很慢的,可以在 pip 时加上镜像源的地址:
python">pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包的名字
这里有一点很关键!不能忘记!Pyinstaller 也是第三方的包,所以新的环境里面一定一定要 pip install Pyinstaller!
exe%E5%A4%A7%E5%B0%8F%EF%BC%88%E9%9D%9E%E5%BF%85%E8%A6%81%E6%AD%A5%E9%AA%A4%EF%BC%89">3、追求极致的exe大小(非必要步骤)
如果你想让你的 Python 程序打包后的 exe 大小,小到不能再小的话,那么就要尽可能地删去虚拟环境里面的一些用不到的包(用 pip uninstall 来删)。
我这里有一个环境的包,它已经把一般程序用不到的包删干净了(没有第三方包)。你可以参考一下(通过输入 conda list 命令来查看)。
多 Python 文件的打包
或许对于单个文件而言,你已经清楚该怎么做了,但是对于多个 Python 文件同时打包而言,你未必清楚。
我相信我们大多数人在编写大项目的时候,都会将一个程序拆解成多个 Python 文件以便于维护。但是前面的打包方式又只能打包一个 Python 文件,其他的 Python 文件就只能作为资源文件放在外面。但是这样别人使用这个程序的时候,不就能看到那些在外面的 Python 文件的源代码了吗?谁愿意把源代码给别人免费看呢?
所以,我们就要将多个 Python 文件同时打包!这里要说一点,这里的多个 Python 文件同时打包时,还是要使用 -F 参数,生成一个文件,
这里可以创建虚拟环境来打包,也可以不用。
1、生成spec文件
同之前的步骤一样,先打开 Anaconda Prompt,然后输入如下命令以生成 Python 源文件 name.py 的 spec 文件,这里的 name.py 一般选取多个 Python 文件的主文件。
python">pyi-makespec -option1 -option2 -... name.py
option 参数和之前步骤里的一样,输入你需要的参数即可。回到我们源代码的文件夹中,可以发现已经多了一个 name.spec 的文件了。
2、编辑spec文件
spec 文件实际上就是一个文本格式的文件,可以用任意文本编辑器打开,也可以用你的 IDE 直接打开,细心的人会发现,里面的内容实际上就是 Python 格式的代码,只不过文件扩展名改成了 spec 而已。
spec 文件实际上就包含了打包的所有参数,我们可以对其进行修改,以达到自定义打包的效果。
找到下面的这句:
它实际上就是个列表!将你需要的 Python 文件的路径(允许相对路径)都以字符串的形式写进这个列表里面,如果是与 name.py 不在同一目录(同一文件夹)下的 Python 文件,那就要写它的绝对路径。编辑完之后,记得保存文件。
3、以spec文件进行打包
回到之前的命令行(Anaconda Prompt),输入以下命令进行打包。
python">Pyinstaller name.spec
慢慢等待,打包完之后,就是我们想要的 exe 文件了,它把所有的 Python 文件都加了进去!但是很可惜,资源文件还是要放在同一目录下才可以正确运行 exe 程序。不过我还是极力推荐这种方式!我每次打包就是用的这一种方式,毕竟资源文件也不是必须打包进 exe 才好的,有些时候,我们的 Python 代码一般都不会超过 1MB 吧(想必大部分人都没有),而资源文件却有大几十甚至几百 MB,打包进去之后,会使得 exe 程序运行变慢,这不好。
而且,你想啊,现在大部分的软件,资源文件啊什么的不都是放在 exe 外面的么?
包含资源文件的打包
这个打包方式,就是对多个 Python 文件的打包方式的补充。
0、一点吐槽
说到这个,我不得不吐槽一句,网上大部分的打包资源文件的方法都是一模一样的,繁琐且复杂,而且好多根本都实现不了,搞得我当时初学的时候一脸懵……,什么引用 os、sys 库搞些什么路径操作啊什么的,辣么麻烦,还有什么把图片文件用 base64 硬编码的啊什么的,也不解释原理,只能说离谱……
Python 的简约风格都被他们忘得一干二净了!对于打包资源文件的路径问题,虽然它打包后认不得相对路径,但是绝对路径总是认得的嘛,没有程序不认识绝对路径!
有人说绝对路径改不了啊,到别人的电脑上怎么运行呢?我只能说……你见识短浅了!一个简单的装饰器知识就可以解决的问题!
好了,不吐槽了,继续说正事。
多个 Python 文件的打包还是和之前讲到的一样,这里只说资源文件的方法。也是一样的,编辑 spec 文件。
1、编辑spec文件
我的建议是,把资源文件(或者文件夹)都统一放在一个与 Python 文件同一目录下的的 res 文件夹里,方便打包,就如下图。
然后,将下图中标识的这一行改成这样:
改完记得保存!
2、修改文件打开函数
这里有三种方法,前两种是我的方法,最后一种是网上别人的方法。
① 引入特定的模块
这个模块的代码很简单,放在下面,一定要将模块命名为 _.py,并在引用其他第三方模块之前就引用它,但又一定要在下划线开头的模块之后引用(否则会有 BUG),它在主 Python 文件里引用一次即可(其他的文件不用引用)!它可以将 open 函数改成我们想要的,而且原来的代码还完全不用修改!
python">import builtins
def wrapper(function):
def _open(*args, **kw):
""" 修改路径 """
_args = list(args)
_args[0] = __file__[:-4] + args[0]
if kw.get('file'):
kw['file'] = __file__[:-4] + kw['file']
return function(*_args, **kw)
return _open
setattr(builtins, 'open', wrapper(open))
这里的 __file__ 是 Python 文件的属性,是一个字符串,为该文件的绝对路径,不管该文件在哪里,__file__ 都是对应的绝对路径。然后我们用写一个 wrapper 函数充当装饰器,将内置的 open 函数包装一下。再引入 builtins 模块(内置函数和类的模块),给其添加一个名为 open 新属性以覆盖原来的 open 函数,并对该项目整体生效即可!
这种方法的好处在于,它只需要在主文件里引用一次即可,其他的什么都不用改!(④ 特别注意 里的除外)
② 自己手动修改 open 函数修改路径
这个修改是在源代码中修改的(每一个用到了 open 函数的 Python 文件都要改一次),目的就是要让相对路径变成会根据主 Python 文件的路径而变化的绝对路径。修改的装饰器如下:
python"># 编写装饰器
def wrapper(function):
def _open(*args, **kw):
""" 修改路径 """
args_list = list(args)
key = '/'.join(__file__.split('\\')[:-1]) + '/'
args_list[0] = key + args[0]
if kw.get('file'):
kw['file'] = key + kw['file']
return function(*args_list, **kw)
return _open
# 装饰内置函数open
open = wrapper(open)
把这段代码写在文件的开头即可(或者说在使用open函数之前)。
③ 网友的其他方法
他们就是写了这样一个函数来代替 open,也是手动修改的 open 函数,不得不说,看起来有点麻烦(每个用了 open 函数的 Python 文件都要引入 os 和 sys 模块)。
python">import os
import sys
def get_resource_path(relative_path):
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
其他的都是一样的。
④ 特别注意
这里还要提一下,无论是前面的哪一种方法,只要你使用了参数为路径的其他函数时,也要改一下,其实就是在相对路径前面加上方法②中的 key 即可。
其实直接方法③来代替也可以,但是功能上容易出错,而且如果 Python 文件较多,那么每个 Python 文件都这样引用两个模块(sys 和 os),看起来比较麻烦。
当我们使用了 tkinter 模块的时候,PhotoImage 类就是要这样写的一个例子(其中的 __init__ 方法用到了路径):
python">class PhotoImage(tkinter.PhotoImage):
def __init__(self, *args, **kw):
if kw.get('file'):
key = '/'.join(__file__.split('\\')[:-1]) + '/'
kw['file'] = key + kw['file']
tkinter.PhotoImage.__init__(self, *args, **kw)
这个代码就要写在使用 PhotoImage 的开头,后续调用时就用这个 PhotoImage,使用其他模块时,遇到参数为路径的函数或类,都要这样修改。
最后一步,和之前的方法一样,打包你的程序即可!
打包实战
我这里以一个我的半成品为例,进行打包。项目是一个图形化界面的程序。我们要将其打包成只含有一个 exe 的文件。
打包方式的选择
我的项目里面包含多个 Python 文件,要用多 Python 文件打包方式;
项目比较大,为节省打包时间,并追求极致的 exe 大小,采用虚拟环境打包方式;
项目含有资源文件夹,采用包含资源文件的打包方式。
打包全过程
项目全部文件(蓝色背景的是主文件)
包含四个 Python 文件、一个资源文件夹(res),资源文件夹里面又包含了一些子文件夹和 json 文件。
第一步:启动 Anaconda Prompt,切换至目标文件夹路径位置
python">cd C:\Users\小康\Desktop\SuperGameLauncher
Pyinstaller%20%E7%9A%84%E7%8E%AF%E5%A2%83%EF%BC%89">第二步:启动虚拟环境(我的是一个纯净的、第三方包只有 Pyinstaller 的环境)
python">conda activate e1
Pyinstaller%20%E5%B7%B2%E7%BB%8F%E5%AE%89%E8%A3%85%E5%A5%BD%E4%BA%86%EF%BC%89">第三步:生成 spec 文件(我的 Pyinstaller 已经安装好了)
python">pyi-makespec -F -w SuperGameLauncher.py
第四步:引入 _.py 模块(我的程序用到了大量 open 函数且涉及多文件)
处理特殊的情况(tkintertools 模块里面有参数为路径的类):
第五步:编辑 spec 文件
修改图中标识的两处地方(_.py 不要忘记了)。
第六步:打包项目(注意这里的对象是 spec 文件)
python">Pyinstaller SuperGameLauncher.spec
打包成功!
第七步:检验打包效果
找到项目中的 dist 文件夹,打开后会有一个 exe 文件。对于我这个项目而言,这已经是非常小的大小了(50.8MB),毕竟资源文件就有 42.2MB,也就是说,除去资源文件,源代码占用的大小为 8.6MB!怎么样,是不是很不错呢?
双击运行!
【看了这么多,我要你一个赞、收藏不过分吧】