註:本文同步刊載在Medium,若習慣Medium的話亦可去那邊看呦!
因為按照慣例,第三十天主要會講比較偏向結論性質的東西,
包含接下來可能的學習方向以及建議,
所以讓我們今天用PyInstaller來做技術方面的最後一章,
恭喜各位讀者,這系列如果都有扎實的跟上的話,
應該能對Python有一些基本的認識。
PyInstaller是一個用來打包安裝Python檔案的函式庫,
一般狀況下,使用pip可以輕鬆將其安裝:
pip install pyinstaller
為什麼我們會需要PyInstaller呢?
一般狀況下有幾個可能:
這時候使用PyInstaller就可以達成這樣的目的,
除了可以給定簡單的加密外(防君子的那種),
它還可以將整個程式連同用到的函式庫一起打包成執行檔!
我們這邊使用前面的tkinter的範例,
讀者應該會用到的檔案就是fromzero.py和unicorn.ico,
還沒做過的同學,請參照Day 22的範例及Day 23的修改部分。
為了測試,
我們再手動加上一個其實我們沒有在這個範例使用的numpy,
請在fromzero.py中額外加入這行:
(因為它是額外pip安裝的,藉此我們可以觀察一下差異)
import numpy as np
PyInstaller的使用方式是直接在命令提示字元下指令,
使用pyinstaller -h可以查看help提示:
pyinstaller -h
...密密麻麻的一大堆XD
我們簡單介紹一下幾個常用的參數部分:
(前面和後面是相同的效果,只是使用縮寫)
-h, --help:顯示help提示說明各參數用法
-F, --onefile:打包成單一一個執行檔
-D, --onedir (預設):打包成一個資料夾,內含一個執行檔
-y, --noconfirm:
--clean:清空前面打包時產生的暫存檔案
-n NAME, --name NAME:將NAME做為app名字並命名到執行檔
(預設會是主程式原先的主檔名)
--add-data <SRC;DEST or SRC:DEST>:
將非二進位檔案加到打包中,
SRC對應原先的檔案,DEST對應打包後放的相對資料夾位置
-p DIR, --paths DIR:如果有額外需要import的函式庫時,
告訴pyinstaller可以去DIR這個位置搜尋
--key KEY:用key來加密Python的bytecode
-w, --windowed, --noconsole:在Windows執行時隱藏命令提示字元的視窗
那麼,我們先試試看最基本的打包:
C:\Users\Desolve\utils>pyinstaller --noconfirm fromzero.py
...底下會開始打包
在預設的狀況下,會產生幾個目錄:
pycache(主程式編譯的bytecode檔),
build(編譯過程中產生的檔案),
dist(最終執行所需要的執行檔及其他資料)
我們切換到dist\fromzero的資料夾以後,
應該可以看到fromzero.exe,以及其它的一些檔案及資料夾,
當中就包含了tk和numpy,
顯然pyinstaller自動幫我們評估將函式庫給包進來了!
那麼,在命令提示字元打fromzero.exe,
或者在資料夾中連點兩下就可以執行了......咦?
C:\Users\Desolve\utils\dist\fromzero>fromzero.exe
Traceback (most recent call last):
File "fromzero.py", line 41, in <module>
File "tkinter\__init__.py", line 2071, in wm_iconbitmap
_tkinter.TclError: bitmap "unicorn.ico" not defined
[12564] Failed to execute script fromzero
顯然我們的獨角獸並沒有被包進去,
由於前面我們讀取檔案時,是在和.py相同的資料夾,
所以我們可以將unicorn.ico複製到dist\fromzero的資料夾,
就可以正常執行了。
當然這顯然不是很理想,所以讓我們回到上一步,
處理一下圖片的部分。
我們先用以下的方式,將unicorn.ico轉成二進位制,
再存到一個.py檔做為變數img:
(註:參考自CSDN blog)
(如果是一般檔案,則可以使用--add-data='SRC;DEST'的方式即可)
>>> import base64 # base64可以將binary檔案轉成unicode格式
>>> icon = open('unicorn.ico', 'rb') # 使用binary的格式讀入icon
>>> b64str = base64.b64encode(icon.read()) # 轉成unicode格式
>>> icon.close()
>>> write = 'img = %s' % b64str # 放到名為img的變數
>>> f = open('icon.py', 'w+') # 寫到icon.py中
>>> f.write(write)
6097
>>> f.close()
此時資料夾中會多出一個icon.py,
裡面就是img="......"的格式。
接著我們要修改我們的fromzero.py,
從讀取原本的icon,改成從icon.py取得變數:
from icon import img # 從icon.py中取得img變數
import base64 # 同樣需要base64函式庫
# 主視窗生成
win = tk.Tk()
win.title('從零開始學Python:第二件X折?')
win.geometry('800x220')
win.resizable(False, False)
# 加上icon
ico = open('unicorn.ico', 'wb+')
ico.write(base64.b64decode(img)) # 寫一個icon出來
ico.close()
win.iconbitmap('unicorn.ico') # 將icon嵌上視窗
os.remove('unicorn.ico') # 把剛剛用完的檔案刪掉
我們在重新下一次指令:
C:\Users\Desolve\utils>pyinstaller fromzero.py -F -y -w
這時候應該同樣可以在dist資料夾看到fromzero.exe,
且由於我們下了-w,所以執行時後面不會出現命令提示字元了!
如果要進行簡單加密的話,
pyinstaller預設是使用tinyaes:
pip install tinyaes
安裝後,打包時額外加上--key="(16個字元的字串)"即可,
少掉的字元會補0。
例如:
C:\Users\Desolve\utils>pyinstaller fromzero.py -F -y -w --key="XDDD3096"
61 INFO: PyInstaller: 4.0
62 INFO: Python: 3.8.5
62 INFO: Platform: Windows-7-6.1.7601-SP1
64 INFO: wrote C:\Users\Desolve\utils\fromzero.spec
66 INFO: UPX is not available.
68 INFO: Extending PYTHONPATH with paths
['C:\\Users\\Desolve\\utils', 'C:\\Users\\Desolve\\utils']
77 INFO: Will encrypt Python bytecode with key: 00000000XDDD3096
77 INFO: checking Analysis
...(以下省略)
如果你覺得這樣子還不夠放心,
可以再透過obfuscator或PyArmor之類的軟體,
將主程式進行混淆以後再打包,
效果會更好呦XD!
最後要留意一點,
如果想要編譯出能在32位元的電腦運行的程式,
則需要使用32位元版本的Python才可以。
那麼,我們就明天見囉!
開啟主程式,看左上角圖示有改變成功,但主程式的檔案圖示及點右鍵開啟內容看圖示依舊是預設的沒有改變成功,請問有方法可以解決這個問題嗎?
看來command還需要加入-i xxx.ico參數,如下:
pyinstaller -F -w -i xxx.ico ooo.py