iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 29
0
Software Development

從零開始學Python系列 第 29

[Day 29] 從零開始學Python - 打包安裝PyInstaller:誰把誰的靈魂,裝進誰的身體

註:本文同步刊載在Medium,若習慣Medium的話亦可去那邊看呦!

因為按照慣例,第三十天主要會講比較偏向結論性質的東西,
包含接下來可能的學習方向以及建議,
所以讓我們今天用PyInstaller來做技術方面的最後一章,
恭喜各位讀者,這系列如果都有扎實的跟上的話,
應該能對Python有一些基本的認識。

PyInstaller是一個用來打包安裝Python檔案的函式庫,
一般狀況下,使用pip可以輕鬆將其安裝:

pip install pyinstaller

為什麼我們會需要PyInstaller呢?
一般狀況下有幾個可能:

  1. 你不想直接讓別人看到程式碼(註:但會破解的還是做得到XD)
  2. 你在開發過程中用了一些函式庫,
    這些函式庫並非內建的,從而別人要用你的.py檔執行的話,
    會需要進行其它的pip install或準備工作。
    這樣很麻煩很不方便,
    你希望拿到的人最好可以滑鼠點兩下就可以執行才對XD

這時候使用PyInstaller就可以達成這樣的目的,
除了可以給定簡單的加密外(防君子的那種),
它還可以將整個程式連同用到的函式庫一起打包成執行檔!

我們這邊使用前面的tkinter的範例,
讀者應該會用到的檔案就是fromzero.py和unicorn.ico,
還沒做過的同學,請參照Day 22的範例及Day 23的修改部分。
https://ithelp.ithome.com.tw/upload/images/20201008/20119871RQGzzgQdt0.jpg
為了測試,
我們再手動加上一個其實我們沒有在這個範例使用的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才可以。

那麼,我們就明天見囉!


上一篇
[Day 28] 從零開始學Python - 深度學習Keras:如果你能預知這條路的陷阱,我想你依然錯得很過癮
下一篇
[Day 30] 從零開始學Python - 結語:少年啊,要忍耐,撐過熬過總算苦盡甘來
系列文
從零開始學Python30

尚未有邦友留言

立即登入留言