簡述
pyinstaller 打包的流程:讀取編寫好的 python 腳本,分析其中調(diào)用的模塊和庫,然后收集這些文件的副本(包括 Python 的解釋器)。最后把副本與腳本,可執(zhí)行文件等放在一個文件夾中,或者可選地封裝在一個可執(zhí)行文件中。
基本使用方法
安裝 pyinstaller
pip install pyinstaller
生成 spec 文件
進(jìn)入主程序目錄,輸入 pyi-makespec -w main.py 生成 main.spec 文件。
幾個常用參數(shù)
| 參數(shù) | 說明 |
|---|---|
| -F,-onefile | 打包一個單個文件 |
| -D,-onedir | 打包多個文件,在 dist 中生成很多依賴文件 |
| -w,-windowed,-noconcole | 當(dāng)程序啟動的時候不會打開命令行(只對windows有效) |
根據(jù)需要編輯 spec 文件
onefile 模式
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['main.py'],
pathex=['C:\\Users\\lunckl\\Desktop\\qcm-python'],
binaries=[('./NovaQCM.exe', '.')], # non-python modules needed by the scripts
datas=[('./*.ico', '.'), ('./*.png', '.'), ('./QCM/*.txt', 'QCM')], # non-binary files included in the app
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='qcm',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
icon='favicon.ico')
onedir 模式
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['main.py'],
pathex=['C:\\Users\\lunckl\\Desktop\\qcm-python'],
binaries=[('./NovaQCM.exe', '.')],
datas=[('./*.ico', '.'), ('./*.png', '.'), ('./QCM/*.txt', 'QCM')],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='qcm',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
icon='favicon.ico')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='QCM')
執(zhí)行打包命令
pyinstaller main.spec
進(jìn)行測試
進(jìn)入生成的 dist 目錄,執(zhí)行 main.exe
常見問題
打包多進(jìn)程、線程
用 pyinstaller 打包好 exe 后,雙擊運(yùn)行,會出現(xiàn)無限循環(huán)地進(jìn)入主程序的情況。此時需要在調(diào)用多進(jìn)程的前面加上如下的代碼:
if __name__ == "__main__":
multiprocessing.freeze_support() # 不加這句,打包的程序就進(jìn)不了下面的子進(jìn)程了
p1 = multiprocessing.Process(target=callback, target=(, ))
p1.start()
- 因為開啟子進(jìn)程是不支持打包 exe 文件的,所以會不停向操作系統(tǒng)申請創(chuàng)建子進(jìn)程,而 multiprocessing.freeze_support() 作用就是支持打包到 windows 的 exe 文件。
- 多進(jìn)程的程序運(yùn)行后,如果直接關(guān)閉控制臺窗口,那么整個程序都會退出,如果是進(jìn)入任務(wù)管理,單獨結(jié)束控制窗口的進(jìn)程,如果子進(jìn)程不是守護(hù)進(jìn)程,那么子進(jìn)程還是會繼續(xù)運(yùn)行。
- 如果是多線程,則沒有這個問題,可以直接打包。
pyinstaller 打包一個 exe 并加入內(nèi)置圖片
編輯生成的 main.spec 文件,修改 datas 列表,添加數(shù)據(jù)的格式為:datas = [('source_path1', 'exe_dir1'), ('source_path2', 'exe_dir2')],可以使用通配符
-
source_path: 資源文件 -
exe_dir: 把資源文件放在 exe 程序中的文件夾??梢灾苯邮褂?.表示把資源文件放在 exe 程序的頂級文件夾中。
最后需要在源代碼的資源路徑引用中進(jìn)行如下修改:
import os
import sys
def resource_path(relative_path):
"""Get absolute path to resource
works for dev and for PyInstaller"""
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
else:
base_path = os.getcwd()
return os.path.join(base_path, relative_path)
pic_path = resource_path('pic.png')
pyinstaller 會將文件夾的路徑信息存儲在 sys.MEIPASS 中,當(dāng)使用的是單文件打包的方式,sys.MEIPASS 的值就是程序運(yùn)行時創(chuàng)建 _MEIxxxxxx臨時目錄的絕對路徑。路徑一般在 C:\Users\user\AppData\Local\Temp\_MEIxxxxx
修改好 .spec 文件和源代碼后,重新打包即可,pyinstaller main.spec
對于以 dir 方式進(jìn)行打包,則只需要修改 .spec 文件,添加資源文件即可。
調(diào)用外部程序使用無命令行窗口模式會出現(xiàn)程序報錯
問題出在 subprocess 上面,簡單來說,打包關(guān)閉了命令行窗口,stdin, stdout 無處安放,參考以下代碼修改即可。
def run_stuff(command_line):
output_filename = 'somefile.txt'
output_file = open(output_filename, "w")
if gui_mode:
result = subprocess.call(command_line, shell=True, stdout=outputFile, stderr=subprocess.STDOUT)
else:
proc = subprocess.Popen(command_line, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE)
proc.stdin.close()
proc.wait()
result = proc.returncode
output_file.write(proc.stdout.read())
參考
PyInstaller打包詳解
pyinstaller 打包成 exe 遇到的一些坑
Python subprocess.call() fails
PyInstaller Manual