Python程序打包--PyInstaller
Python程序打包,首选“PyInstaller”,操作简单,功能丰富,而且它还支持Python2.7和Python3.3+。之前用的py2exe则不支持python3.5。PyInstaller的官网如下:
http://www.pyinstaller.org/
就像官网首页所说的,PyInstaller简单到仅需两条命令即可搞定从安装到打包你的程序:
1)安装PyInstaller
pip install pyinstaller
pyinstaller yourprogram.py
本文不想复述PyInstaller的简单应用,而是通过我遇到的一个案例,来探索PyInstaller的一些稍高级点的应用,它包括:
1)python包装EXE;
2)生成和修改.spec文件;
3)onefolder和onefile;
4)添加icon;
一、需求
我现有一个C/C++程序,它包含两个文件:CheckLicense.exe和EncryptorDll.dll。我一般将它打成一个压缩包给FAE工程师在使用。使用的时候需要先先解压,再运行“CheckLicense.exe”,稍稍有点不方便。
我就想:既然PyInstaller可以将Python程序打包成一个文件,那么有木有可能我用python脚本调用这个“CheckLicense.exe”,然后把“python脚本”、“CheckLicense.exe”和“EncryptorDll.dll”一起用PyInstaller打包成一个EXE文件?
注:有些压缩软件也支持将一个文件夹压缩为一个exe文件,解压缩自动运行指定的文件。但是,这个需要目标电脑安装该压缩软件。
二、探索
1,直接打包
在我之前的博客:http://blog.csdn.net/sagittarius_warrior/article/details/72831039
如果python脚本中通过ctypes调用了某个DLL文件,PyInstaller在打包的时候,除了会自动搜索“依赖的库”、“依赖的python脚本”,还会将“ctypes调用的DLL文件”和该文件依赖的“运行时库”,并打包!是不是很智能?
于是,我想直接写如下脚本,PyInstaller会不会也将我要的文件“CheckLicense.exe”和“EncryptorDll.dll”都打包:
#CheckLicenseEx.py
import os
os.system('CheckLicense')
将该脚本与“CheckLicense.exe”和“EncryptorDll.dll”放在同一个目录下,运行该脚本,可以正常调用“CheckLicense.exe”。
使用如下命令打包:
pyinstaller --onefile CheckLicenseEx.py
在dist目录下生成了“CheckLicenseEx.exe”,运行,跳出一个console,但貌似没有执行“CheckLicense.exe”。
这么说来,直接打包是不行的!
2,修改“spec文件”
通过浏览PyInstaller -> Manual -> Using Spec Files
https://pyinstaller.readthedocs.io/en/stable/spec-files.html
于是知道,如果PyInstaller没有自动打包的文件,可以通过修改spec文件,进行手动添加。步骤如下:
1)生成spec文件
pyi-makespec
options name.py
[other
scripts ...]
对于我的程序,具体命令如下:
pyi -makespec --onefile CheckLicenseEx.py
2)修改spec文件
将需要额外打包的文件,添加到datas项,如下:
datas=[('CheckLicense.exe', '.'), ('EncryptorDll.dll', '.')],
3)使用spec文件生成
pyinstaller CheckLicenseEx.spec
注意:此时仅支持如下选项:
- –upx-dir=
- –distpath=
- –workpath=
- –noconfirm
- –ascii
验证:
将生成的CheckLicenseEx.exe拷贝到目标机器,运行,报错:“CheckLicense.exe不是可以执行文件”。但是,如果我没有用“--onefile”选项,只是生成一个文件夹,将整个文件夹拷贝到目标电脑,可以正常运行CheckLicenseEx.exe,并成功执行“CheckLicense.exe”。
该现象非常令人迷惑!
注:PyInstaller官网文档中说:打包成onefile文件时,发布前,最好先打包成onefolder,并检查一下需要的文件是否正确打包了。onefolder模式的优势就是,方便检查。
3,程序执行路径
对于上面提到的诡异现象,我仔细阅读了PyInstaller的文档关于onefile的工作原理:onefile实际也是在运行时,先解压到一个临时文件夹,然后执行Python脚本。
因此,我想:onefolder可以,而onefile不行,不太可能是文件漏打包了。那么,有一种极大的可能:路径不对,导致执行该条语句时,
os.system('CheckLicense')
python解释器在当前目录下找不到“CheckLicense.exe”。
1)打印路径
为了验证我的想法,我决定先注释上述语句,而改为添加一些打印信息:
#CheckLicenseEx.py
import os
import sys
currPath = sys.path[0]
print(currPath)
print(os.listdir(currPath))
#os.system('CheckLicense')
然后,按照之前的方法打包为onefile —— “CheckLicenseEx.exe”,运行后,报错,指向这一句
print(os.listdir(currPath))
错误信息:currPath不是一个有效的路径。仔细查看currPath,发现其中有“...\***.zip\”。貌似PyInstaller将脚本到打包到了这个压缩文件中,而os.listdir()函数不能列出压缩文件内部的文件列表。
我想,那会不会“CheckLicense.exe”和“EncryptorDll.dll”根本就不在这个压缩文件中,而在它的上层文件夹?
于是修改脚本:
#CheckLicenseEx.py
import os
import sys
currPath = sys.path[0]
highPath = os.path.split(currPath)
print(os.path.split(currPath))
print(os.listdir(highPath[0]))
#print(highPath[0]+'\\CheckLicense.exe')
#os.system('CheckLicense')
至此,问题定位了:“CheckLicenseEx.exe”的运行目录、onefile解压后的python脚本目录和“CheckLicense.exe”所在的目录各不相同。
注:关于python的路径相关函数,可以参考 http://blog.csdn.net/longshen747/article/details/17194259
2)正确路径
我们只需如下修改脚本
#CheckLicenseEx.py
import os
import sys
currPath = sys.path[0]
highPath = os.path.split(currPath)
os.system(highPath[0]+'\\CheckLicense')
再打包,即可正确运行了!!!
4,不修改spec文件
我们需要的是将“CheckLicense.exe”和“EncryptorDll.dll”打包进onefolder或onefile。事实上,PyInstaller文档上还提到了一种方法,直接用“--add-data”命令,也可以办到。命令如下:
pyinstaller --add-data=CheckLicense.exe;. --add-data=EncryptorDll.dll;. --onefile CheckLicenseEx.py
通过这条命令,生成的spec文件中,同样的也能看到datas项里添加了相同的内容。
5,修改程序默认图标
PyInstaller打包的程序,默认图标比较丑,我想美化一下,可以换成自己的图标,可以用“--icon”命令,如下:
pyinstaller --add-data=CheckLicense.exe;. --add-data=EncryptorDll.dll;. --icon=umbrella.ico --onefile --clean CheckLicenseEx.py
其中,最后的“--clean”命令是清缓存。
下面是我的Logo
上一篇: 咖啡再热闹,也逃不出巨头的手掌心