欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python程序打包--PyInstaller

程序员文章站 2022-05-28 08:58:26
...

    Python程序打包,首选“PyInstaller”,操作简单,功能丰富,而且它还支持Python2.7和Python3.3+。之前用的py2exe则不支持python3.5。PyInstaller的官网如下:

     http://www.pyinstaller.org/

    就像官网首页所说的,PyInstaller简单到仅需两条命令即可搞定从安装到打包你的程序:

1)安装PyInstaller

pip install pyinstaller


2)打包python程序

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


    注意:在生成spec文件的时候,需要将其他选项都带上,如“--onefile”。


2)修改spec文件

    将需要额外打包的文件,添加到datas项,如下:

             datas=[('CheckLicense.exe', '.'), ('EncryptorDll.dll', '.')],


    如上,datas是一个list,而该list的内部元素是二元组,前者表示待打包的文件或文件夹,一般来说推荐使用全路径;后者表示打包后的位置,dot表示当前文件夹。

    

3)使用spec文件生成

pyinstaller CheckLicenseEx.spec


注意:此时仅支持如下选项:

  • –upx-dir=
  • –distpath=
  • –workpath=
  • –noconfirm
  • –ascii
而像“--onefile”等已经不支持了,故需要在生成spec文件时将那些命令先输入。


验证:

    将生成的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')


    还是按照上述方法打包成onefile,运行后发现:“CheckLicense.exe”和“EncryptorDll.dll”果然在那个压缩文件的上层目录。

    至此,问题定位了: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

Python程序打包--PyInstaller