2023年7月2日 星期日

Python 學習筆記 : 用 pyinstaller 將 .py 程式轉成 .exe 執行檔

最近兩周用 tkinter 打造的維運自動化軟體完工後要佈署到 Windows 作業環境中來運行, 當然不能用開發測試階段那樣用命令列的 python 指令去執行原始碼, 因為那必須在作業電腦中也安裝 Python, 這種使用方式不僅麻煩 (還要開啟命令提示字元視窗), 而且有時作業電腦並不允許裝 Python. 可行的方法是將原始碼轉成 .exe 可執行檔, 它會自帶 Python 執行環境, 執行此程式的別台電腦不需要安裝 Python. 參考 : 



1. 安裝 pyinstall 套件 : 

用系統管理員身分開啟命令提示字元視窗安裝 pyinstaller 套件 (我第一次不是用系統管理員身分開啟命令提示字元視窗, 結果安裝失敗) : 

pip install pyinstaller   

C:\WINDOWS\system32>pip install pyinstaller     
Collecting pyinstaller
  Downloading pyinstaller-5.13.0-py3-none-win_amd64.whl (1.3 MB)
     ---------------------------------------- 1.3/1.3 MB 3.8 MB/s eta 0:00:00
Requirement already satisfied: setuptools>=42.0.0 in c:\python311\lib\site-packages (from pyinstaller) (65.5.0)
Collecting altgraph (from pyinstaller)
  Downloading altgraph-0.17.3-py2.py3-none-any.whl (21 kB)
Collecting pyinstaller-hooks-contrib>=2021.4 (from pyinstaller)
  Downloading pyinstaller_hooks_contrib-2023.4-py2.py3-none-any.whl (271 kB)
     ---------------------------------------- 271.8/271.8 kB 16.3 MB/s eta 0:00:00
Collecting pefile>=2022.5.30 (from pyinstaller)
  Downloading pefile-2023.2.7-py3-none-any.whl (71 kB)
     ---------------------------------------- 71.8/71.8 kB 4.1 MB/s eta 0:00:00
Collecting pywin32-ctypes>=0.2.1 (from pyinstaller)
  Downloading pywin32_ctypes-0.2.2-py3-none-any.whl (30 kB)
Installing collected packages: altgraph, pywin32-ctypes, pyinstaller-hooks-contrib, pefile, pyinstaller
Successfully installed altgraph-0.17.3 pefile-2023.2.7 pyinstaller-5.13.0 pyinstaller-hooks-contrib-2023.4 pywin32-ctypes-0.2.2


2. 執行 pyinstaller 將 .py 原始碼轉成 .exe : 

如果要將原始碼轉換成單一個 .exe 執行檔要在 pyinstaller 指令後面加一個 -F 參數, pyinstaller 會在目前目錄下建一個 dist 子目錄來存放此 exe 檔 : 

pyinstaller -F 原始碼檔案.py     

例如原始碼為 test.py : 

E:\test>pyinstaller -F test.py    
662 INFO: PyInstaller: 5.13.0
662 INFO: Python: 3.11.3
674 INFO: Platform: Windows-10-10.0.19045-SP0
726 INFO: wrote E:\test\test.spec
825 INFO: Extending PYTHONPATH with paths
['E:\\test']
1148 INFO: checking Analysis
1148 INFO: Building Analysis because Analysis-00.toc is non existent
1148 INFO: Initializing module dependency graph...
1157 INFO: Caching module graph hooks...
1187 INFO: Analyzing base_library.zip ...
3265 INFO: Loading module hook 'hook-heapq.py' from 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks'...
3389 INFO: Loading module hook 'hook-encodings.py' from 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks'...
5530 INFO: Loading module hook 'hook-pickle.py' from 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks'...
8728 INFO: Caching module dependency graph...
8908 INFO: running Analysis Analysis-00.toc
8935 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
  required by C:\Python311\python.exe
9018 INFO: Analyzing E:\test\test.py
9265 INFO: Loading module hook 'hook-idlelib.py' from 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks'...
9454 INFO: Loading module hook 'hook-difflib.py' from 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks'...
9802 INFO: Loading module hook 'hook-multiprocessing.util.py' from 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks'...
9975 INFO: Loading module hook 'hook-xml.py' from 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks'...
11103 INFO: Loading module hook 'hook-platform.py' from 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks'...
12725 INFO: Loading module hook 'hook-sysconfig.py' from 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks'...
12988 INFO: Processing module hooks...
13088 INFO: Loading module hook 'hook-_tkinter.py' from 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks'...
13090 INFO: checking Tree
13090 INFO: Building Tree because Tree-00.toc is non existent
13093 INFO: Building Tree Tree-00.toc
13297 INFO: checking Tree
13297 INFO: Building Tree because Tree-01.toc is non existent
13298 INFO: Building Tree Tree-01.toc
13337 INFO: checking Tree
13337 INFO: Building Tree because Tree-02.toc is non existent
13338 INFO: Building Tree Tree-02.toc
13379 INFO: Looking for ctypes DLLs
13427 INFO: Analyzing run-time hooks ...
13430 INFO: Including run-time hook 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py'
13433 INFO: Including run-time hook 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py'
13436 INFO: Including run-time hook 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py'
13439 INFO: Including run-time hook 'C:\\Python311\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth__tkinter.py'
13469 INFO: Looking for dynamic libraries
456 INFO: Extra DLL search directories (AddDllDirectory): []
456 INFO: Extra DLL search directories (PATH): ['C:\\Python311\\Scripts\\', 'C:\\Python311\\', 'C:\\Python37\\Scripts\\', 'C:\\Python37\\', 'C:\\Program Files (x86)\\Intel\\Intel(R) Management Engine Components\\iCLS\\', 'C:\\Program Files\\Intel\\Intel(R) Management Engine Components\\iCLS\\', 'C:\\WINDOWS\\system32', 'C:\\WINDOWS', 'C:\\WINDOWS\\System32\\Wbem', 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\', 'C:\\WINDOWS\\System32\\OpenSSH\\', 'C:\\Program Files (x86)\\Intel\\Intel(R) Management Engine Components\\DAL', 'C:\\Program Files\\Intel\\Intel(R) Management Engine Components\\DAL', 'C:\\Program Files\\Intel\\WiFi\\bin\\', 'C:\\Program Files\\Common Files\\Intel\\WirelessCommon\\', 'C:\\Program Files\\Git\\cmd', 'C:\\Program Files\\nodejs\\', 'C:\\ProgramData\\chocolatey\\bin', 'C:\\Users\\User\\AppData\\Local\\Microsoft\\WindowsApps', 'C:\\Program Files\\Bandizip\\', 'C:\\Users\\User\\AppData\\Local\\atom\\bin', 'C:\\Users\\User\\AppData\\Roaming\\npm']
14443 INFO: Looking for eggs
14450 INFO: Using Python library C:\Python311\python311.dll
14450 INFO: Found binding redirects:
[]
14469 INFO: Warnings written to E:\test\build\test\warn-test.txt
14510 INFO: Graph cross-reference written to E:\test\build\test\xref-test.html
15059 INFO: checking PYZ
15059 INFO: Building PYZ because PYZ-00.toc is non existent
15060 INFO: Building PYZ (ZlibArchive) E:\test\build\test\PYZ-00.pyz
17368 INFO: Building PYZ (ZlibArchive) E:\test\build\test\PYZ-00.pyz completed successfully.
17478 INFO: checking PKG
17478 INFO: Building PKG because PKG-00.toc is non existent
17480 INFO: Building PKG (CArchive) test.pkg
29536 INFO: Building PKG (CArchive) test.pkg completed successfully.
29609 INFO: Bootloader C:\Python311\Lib\site-packages\PyInstaller\bootloader\Windows-64bit-intel\run.exe
29609 INFO: checking EXE
29610 INFO: Building EXE because EXE-00.toc is non existent
29610 INFO: Building EXE from EXE-00.toc
29616 INFO: Copying bootloader EXE to E:\test\dist\test.exe.notanexecutable
29800 INFO: Copying icon to EXE
29805 INFO: Copying icons from ['C:\\Python311\\Lib\\site-packages\\PyInstaller\\bootloader\\images\\icon-console.ico']
29837 INFO: Writing RT_GROUP_ICON 0 resource with 104 bytes
29837 INFO: Writing RT_ICON 1 resource with 3752 bytes
29839 INFO: Writing RT_ICON 2 resource with 2216 bytes
29839 INFO: Writing RT_ICON 3 resource with 1384 bytes
29842 INFO: Writing RT_ICON 4 resource with 37019 bytes
29843 INFO: Writing RT_ICON 5 resource with 9640 bytes
29843 INFO: Writing RT_ICON 6 resource with 4264 bytes
29844 INFO: Writing RT_ICON 7 resource with 1128 bytes
29912 INFO: Copying 0 resources to EXE
29912 INFO: Embedding manifest in EXE
29914 INFO: Updating manifest in E:\test\dist\test.exe.notanexecutable
29952 INFO: Updating resource type 24 name 1 language 0
30012 INFO: Appending PKG archive to EXE
30339 INFO: Fixing EXE headers
32482 INFO: Building EXE from EXE-00.toc completed successfully.

我的 Python 環境是 3.11 版, 可見雖然上面參考的文章大都說 Python 3.9 以下都沒問題, 我實際測試 3.11 也是 OK 的. 轉換完畢會在目前資料夾下出現一個 dist 子目錄用來存放轉換得到的 .exe 檔 科切換到 dist 下看看是否能執行 :

E:\test>cd dist    

E:\test\dist>dir    
 磁碟區 E 中的磁碟沒有標籤。
 磁碟區序號:  03B3-666A

 E:\test\dist 的目錄

2023/07/02  上午 12:16    <DIR>          .
2023/07/02  上午 12:16    <DIR>          ..
2023/07/02  上午 12:16        12,179,107 test.exe   
               1 個檔案      12,179,107 位元組
               2 個目錄  399,272,574,976 位元組可用

要注意的是, 此 .exe 檔只包含執行原始碼的解譯環境, 並不包括程式會用到的資料夾或檔案, 因此若程式中有用到外部檔案 (例如 logo.ico 圖標檔案) 或目錄 (例如存放紀錄檔的 /log), 必須將它們從開發目錄複製到 dist 下面, 否則執行 test.exe 時會出現錯誤. 

注意, 如果是在檔案總管下點擊 test.exe, 發生錯誤時會閃退, 根本不知道發生甚麼事, 必須開啟命令提示字元視窗用命令列執行 test.exe 才會報出錯誤原因, 例如缺了圖標 logo.ico 會這樣 : 

E:\test\dist>test.exe   
Traceback (most recent call last):
  File "test.py", line 554, in <module>
  File "tkinter\__init__.py", line 2136, in wm_iconbitmap
_tkinter.TclError: bitmap "logo.ico" not defined
[21080] Failed to execute script 'test' due to unhandled exception!

補了 logo.ico 還缺了 /log 子目錄結果如下 :

E:\test\dist>test.exe   
Traceback (most recent call last):
  File "test.py", line 724, in <module>
  File "test.py", line 227, in load_log_list
FileNotFoundError: [WinError 3] 系統找不到指定的路徑。: './log'
[12768] Failed to execute script 'test' due to unhandled exception!

只要所需的檔案目錄都複製到 dist 下面後就可以順利執行了. 如果要以綠色軟體方式執行, 只要將 dist 目錄複製到別台電腦, 然後將 exe 傳送到桌面當捷徑, 點擊捷徑即可執行無須安裝; 如果需要打包成安裝程式, 可以用 Inno setup 之類的打包軟體來將整個 dist 下的程式, 檔案, 與目錄打包成單一個 setup.exe, 參考 :



2023-07-04 補充 : 

最近幾天數據處理工作量較大, 所幸這套軟體及時派上用場, 自動化讓作業速度提升好幾倍, 不需要像以前那樣用 Excel 或 EditPlus 修修改改, 花這兩周的時間太值得啦! 不過程式執行時會先出現一個無內容的命令提示字元視窗 (我猜是用來啟動 Python 環境), 然後才出現 Tkinter 視窗 : 




我從下面這篇文章得知, 原來在轉換時可以用 -w 參數讓命令提示字元視窗不要出現 : 


例如 : 

pyinstaller -F 原始碼檔案.py  -w      

兩周內改版至第 15 版, 原始碼 1200 行左右, 約 57KB, 轉成 .exe 檔後約 12MB. 


2023-07-24 補充 : 

經過 4 周我又寫好一個 tkinter 的 GUI 軟體 (CSR 相關), 為了避開資安查核要求, 程式不另外用 SETUP 軟體打包, 均以 portable 方式執行, 在佈署時直接將主程式傳送到桌面當捷徑, 這時就需要一個 logo.ico 來在桌面標示此程式, 這可以用小畫家等工具製作一個 32x32 的 .bmp 圖標檔, 利用下面的線上轉檔工具轉成 .ico 檔 :


如果需要將圖標圓角化, 可以先將圖檔用下列網站處理 :


參考 :


不過此 .ico 是在程式執行時顯示在左上角作為圖標, 並不會在桌面顯示, 如果要將此 logo.ico 也當作桌面捷徑圖標, 在用 pyinstaller 時需加入 --icon=logo.ico 參數, 例如 : 

pyinstaller --onefile --icon=logo.ico 原始碼檔案.py -w     

沒有留言 :