用 cx_Freeze 將 Python 腳本編譯為 Windows exe 實戰

(歡迎分享源網頁,禁止未經本人書面許可轉載至他處,或進行其他不當使用行為)

Python 腳本在裝有 Python 的系統中可以直接雙擊運行,但絕大多數普通用戶並沒有配置此類環境,而編譯為可執行二進位文件後,用戶無需預先安裝 Python 及依賴庫即可像運行普通程序一樣運行您的代碼。

有相當數量的 Python 庫可以實現此類轉換,著名的有 py2exe、py2app、PyInstaller、cx_Freeze 等。其中,py2app 適用於 macOS,不在本文討論範圍內;py2exe、PyInstaller 因為 Python 3.6 通過 PEP 523 等修改了代碼對象(code object)的實現而暫未能繼續支持。因此,cx_Freeze 屬於目前可用的最佳方案之一。

根據其官方文檔,cx_Freeze 需要通過一個簡單的安裝腳本來進行構建。一般來說,只要 Python 腳本本身能無錯運行在自身環境,且 cx_Freeze distutils 安裝腳本配置得當,那麼構建出的可執行文件就能在任何相同操作系統下運行。作為開始,首先充分調試自己的 Python 腳本(不妨假設其名為 main.py)。main.py 允許從任何庫或其他腳本中導入,甚至用 os.system() 或 subprocess.run() 等調用其他程序。確認無誤後,在 main.py 所在目錄下新建一個較簡單的 cx_Freeze 腳本,不妨假設其名為 setup.py,內容如下:

import sysnfrom cx_Freeze import setup, Executablenn# Dependencies are automatically detected, but it might need fine tuning.nbuild_exe_options = {packages: [], excludes: []}nnsetup( name = <程序名>,n version = <程序版本>,n description = <程序描述>,n options = {build_exe: build_exe_options},n executables = [Executable(main.py)])n

可見其基本遵照了官方文檔的示例,並去除了一些暫時用不到的語句。其中,build_exe_options 暫時留空,待第一次構建後根據可能的報錯信息補充;原例中的 base 實際上默認就是 None,即命令行程序,除非需要構建圖形界面程序,否則配置了反而是畫蛇添足。現在可以打開命令行終端,在此目錄下運行

python setup.py buildn

所構建的可執行文件為 .buildexe.win-<架構名>-<Python 版本號>main.exe。不幸的是,一般而言,除非 main.py 本身過於簡單,很少有能一次編譯通過且 exe 正確運行的情況,只要有耐心將錯誤各個擊破,最終還是有希望成功的。以下總結一些常見報錯信息,注意很多時候這些錯誤並不是出現在編譯時(即使 cx_Freeze 輸出大量 ? 開頭的缺失庫也未必是問題所在),而是在運行 exe 時才會彈窗。部分問題在使用 py2exe、PyInstaller 等其他工具時也會出現(因為基本都依賴於 Python 代碼對象),因而下列條目亦有一定參考價值。

1. 看似編譯成功,一旦運行 exe 則報找不到某些庫或導入錯誤,例如

ImportError: No module named <任意庫名>n

這是由於 cx_Freeze 在編譯時,實際上是將全部導入庫都編譯了一遍並放在子目錄中供主程序調用,從而真正獨立於 Python 環境。其自稱能夠自動分析哪些庫需要包含。然而事實上對深入一定層次後的導入分析有瑕疵(例如 main.py 引用了一個庫,該庫又引用了另一個庫,則第三個庫有可能分析不出)。遇到這種錯誤,絕大多數情況下可以手動在 setup.py 中指定額外包含的庫,既可以在 build_exe_options 下的 packages 中指定庫名,也可以在 build_exe_options 中新建一個鍵名為 includes 然後指定庫名,形如:

build_exe_options = {packages: [tkinter, scipy], includes: [numpy.core._methods]}n

這個過程有時要重複多次,直到添加完所有<del>屆不到</del>不能自動檢測到的庫。據說也可以直接在 setup.py 中導入(import)庫,未親測。個別特例無效,請繼續閱讀。

2. 對 scipy.spatial.ckdtree,即使手動添加包含後,運行時依然報 ImportError,這是由於 cx_Freeze 編譯時對該庫文件的命名大小寫與 Python 代碼中不一致,而 Python 大小寫敏感。解決方法是在 .buildexe.win-<架構名>-<Python 版本號>scipyspatial 下找到 cKDTree.cp<Python 版本號>-win_<架構名>.pyd,重命名為 ckdtree.cp<Python 版本號>-win_<架構名>.pyd,不用重新編譯(不然下次文件名又錯了),再次運行即不會報錯。當然可以在 setup.py 中添加語句,在每次編譯完畢後自動重命名該文件,步驟不再贅述。

3. 編譯時直接報錯 KeyError: TCL_Library。此問題在 Python 3.5 或以上版本中出現,解決方法是在 setup.py 中 setup() 之前添加

import osnos.environ[TCL_LIBRARY] = $PYTHONDIR$tcltcl8.6nos.environ[TK_LIBRARY] = $PYTHONDIR$tcltk8.6n

4. 運行 exe 時報錯:

Traceback (most recent call last):n File "$PYTHONDIR$libsite-packagescx_freezeinitscriptsConsole.py"nline 21, in <module>n exec(code, m.__dict__)n File "Widgets_tmp.py", line 1, in <module>n File "$PYTHONDIR$libtkinter__init__.py", line 35, in <module>n import _tkinter#If this fails you Python may not be configured for TknImportError: DLL load failed:n

顧名思義,導入 Tk 相關 DLL 出錯,解決方法是手動導入。在 build_exe_options 中添加鍵

include_files: [$PYTHONDIR$DLLstcl86t.dll, $PYTHONDIR$DLLstk86t.dll]n

即可解決。

5. 注意如果調用了其他 exe,請設法確保用戶也能使用和您的腳本中相同的方式在命令行中直接調用之,例如直接將該 exe 放在 main.exe 所在目錄下(當然,如果原腳本中用到了絕對路徑,只能說您根本沒打算讓它移植到別的機子上),或設置好 PATH 環境變數。

6. 最後,確認 main.exe 正常運行,然後將整個 exe.win-<架構名>-<Python 版本號> 目錄打包。沒有其餘文件單獨運行 main.exe 顯然是不行的,因此記得提醒用戶。

朋友們如果在使用過程中遇到了上文沒有提及的其他錯誤,歡迎在評論中一起切磋,共同提高。

參考文獻:

[1] cx_Freeze and seaborn - ImportError: No module named 'scipy.spatial.ckdtree'

[2] KeyError: 'TCL_Library' when I use cx_Freeze

[3] Error on running tkinter exe made with cx_freeze

推薦閱讀:

TAG:Python | cx_freeze | Windows开发 |