Pyinstaller打包用spec新增資原始檔

王平發表於2019-02-20

最近寫的翻譯軟體——transdocx,就是給普通使用者而非Python程式設計師用的,所以它必須是一個開箱即用的軟體,普通使用者下載下來就能雙擊執行。

Pyinstaller打包用spec新增資原始檔

而Python作為一個指令碼語言,要執行是必須有直譯器的,它不能像C/C++那樣編譯成二進位制。同樣,也不能要求普通使用者首先安裝Python直譯器、再安裝依賴的包、最後執行transdocx。所以,需要把Python寫好的軟體打包成一個exe程式,讓使用者雙擊既可以使用。打包Python程式的最好的工具可能就是pyinstaller了。

下面我就結合transdocx打包的過程來講講pyinstaller的使用,平臺是Windows,Linux和macOS類似。

一、用pyinstaller給純Python程式碼打包

如果你的軟體中只有.py檔案,即Python程式碼檔案,不包括圖示、圖片等資原始檔,那麼使用pyinstaller打包是非常簡單的,往往只需要下面一行命令即可:

pyinstaller -F -w -i icon.ico transdocx.py

第一版本的transdocx就是這樣完成的。其中的幾個選項:
-F 把整個軟體(包括依賴的各種庫檔案)打包成單一檔案;
-w 禁止Windows的命令列視窗。不然雙擊exe時會開啟一個黑乎乎的dos視窗;
-i 生成的exe檔案會帶有這個圖示,有識別度也更好看;
最後的transdocx.py就是翻譯軟體的入口程式。

執行完上面的命令,會在當前目錄下生成一個transdocx.spec檔案和兩個資料夾build和dist,其中dist裡面就是最終生成的exe檔案,把這個檔案發給普通使用者就可以了。當然,你要先自己測試沒有問題。

transdocx.spec 是於pyinstaller生成的配置檔案,下次打包時,可以不執行上面帶引數的命令,而直接執行:

pyinstaller transdocx.spec

二、用pyinstaller給帶有資原始檔的程式碼打包

第二版為了支援PDF,新增了目錄bin/下面的相關資原始檔,主要是:
pdftotext.exe : Windows下面的提取PDF文字內容的命令列工具;
pdftotext : Linux下面提取PDF文字內容的命令列工具;
default.docx : 生成docx的預設模板檔案。

提取PDF內容是通過Python的subprocess模組呼叫命令列工具pdftotext.exe 實現的。如果按照上面比較簡單的打包方式,就會報錯:找不到這個命令列工具。因為這些資原始檔沒有被打包到最終的exe檔案。

要把這些資原始檔包含進去,可以給pyinstaller新增選項,也可以修改spec檔案。我通過修改transdocx.spec來實現:

給spec新增資原始檔路徑

(關於spec的說明可以檢視pyinstaller的官方文件)
新增過程還是很簡單的,看圖中紅框部分就是。
前面pyinstaller 自動生成的spec檔案中,binaries原本是空的:

binaries=[]

就是一個空的list,把要新增的資原始檔以tuple的形式傳入,tuple的第一個元素是資原始檔的路徑,第二個元素是打包後存放資源的資料夾。比如:

(‘./bin/pdftotext.exe’, ‘bin’)

就是把 ‘./bin/pdftotext.exe’ 打包後放到bin目錄下面。打包後的目錄跟Python程式碼的目錄結構一直即可,pdftotext.exe原先放在bin下面可以讓程式執行,那麼打包後也放在bin下面即可。

在spec檔案中新增好要包含的資原始檔再次打包就可以把資原始檔打包到最終的exe檔案了。然而,這時得到的最終exe還是不能執行! 報的錯誤還是找不到相關檔案。

三、修改資原始檔路徑以保證打包結果能執行

為什麼還不能執行?這樣從pyinstaller打包後的exe的執行機制講起。打包得到的exe檔案是一個可自解壓的程式,它會把這個exe檔案中包含的檔案打包到一個名為 _MEIxxxxxx 的臨時目錄下面,這個目錄在系統的臨時資料夾下面(Linux下是 /tmp),當程式退出時,會自動清空刪除這個臨時目錄 _MEIxxxxxx。

我們先來看看這個臨時目錄 _MEIxxxx 裡面都是些什麼,下面截圖是Linux下面的,Windows類似,只不過路徑不一樣,Windows下是.dll等。

spec mei臨時目錄

其中的bin目錄下就是我新增的資原始檔。

最終的exe檔案有可能放在任何目錄執行,其當前目錄下不會有bin目錄下面的資原始檔,而是被解壓到了臨時目錄下面,所以程式報錯找不到相關檔案。

因此,我們要在程式中指定資原始檔的路徑,使得它在非打包模式和打包模式下執行時都能找到相關資原始檔。這需要新增一個路徑解析函式:

給spec新增一個路徑解析函式

這個函式很簡單,把資源的相對路徑轉換為絕對路徑。如果找到 _MEIPASS 路徑就以此為資源的基準路徑,否則以當前路徑為基準路徑。

程式碼中任何使用資原始檔的相對路徑都用該函式轉換一下即可保證資原始檔可以被找到。比如程式碼中:

轉換spec資原始檔路徑

可以從transdocx的原始碼中查詢更多resource_path的示例。

至此,打包的問題完美解決了。再次使用spec打包一下,看看exe應該可以正常執行了。

雙擊exe執行正常,選取一個PDF檔案,點選“翻譯”。納尼?!又報錯!!

雙擊pyinstaller打包後的程式出錯

一番搜尋後,stackoverflow上找到了原因:
https://stackoverflow.com/questions/337870/

問題出在subprocess上面:

要使用subprocess pipe來打包

簡單來說就是,打包是關閉了命令列視窗,stdin, stdout 無處安放。
所以,把它們用subprocess.PIPE 管道代替即可。
shell=True 可以防止執行subprocess.Popen()時閃現一個黑糊糊的dos視窗。

這時候,才算完美解決了pyinstaller打包的問題。總結一下pyinstaller打包的過程:
(1)pyinstaller -F -w xxx.py;
(2)修改上一把生成的xxx.spec檔案,新增資原始檔;
(3)pyinstaller xxx.spec 打包為exe檔案。

如果你在使用pyinstaller的過程中,遇到和解決了一些問題,歡迎在下面留言和大家一起討論分享。

pyinstaller擴充閱讀
用Python寫一個翻譯doc文件的小軟體
給寶寶的翻譯小軟體(續):支援PDF啦!
用pyinstaller打包你的Python程式並繫結CPU

猿人學banner宣傳圖

我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。

***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***

相關文章