Featured image of post 從零開始學Python (29) — 打包安裝PyInstaller:誰把誰的靈魂,裝進誰的身體

從零開始學Python (29) — 打包安裝PyInstaller:誰把誰的靈魂,裝進誰的身體

Day 29 打包安裝PyInstaller:誰把誰的靈魂,裝進誰的身體

註:本篇文章同步刊載於iT邦幫忙,為鐵人賽之系列文章。
https://ithelp.ithome.com.tw/articles/10247305

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

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

1
pip install pyinstaller

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

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

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

我們這邊使用前面的tkinter的範例,
讀者應該會用到的檔案就是fromzero.py和unicorn.ico,
還沒做過的同學,請參照Day 22的範例及Day 23的修改部分。

為了測試,
我們再手動加上一個其實我們沒有在這個範例使用的numpy,
請在fromzero.py中額外加入這行:
(因為它是額外pip安裝的,藉此我們可以觀察一下差異)

1
import numpy as np

PyInstaller的使用方式是直接在命令提示字元下指令,
使用pyinstaller -h可以查看help提示:

1
2
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執行時隱藏命令提示字元的視窗

那麼,我們先試試看最基本的打包:

1
2
C:\Users\Desolve\utils>pyinstaller --noconfirm fromzero.py
...底下會開始打包

在預設的狀況下,會產生幾個目錄:
pycache(主程式編譯的bytecode檔),
build(編譯過程中產生的檔案),
dist(最終執行所需要的執行檔及其他資料)

我們切換到dist\fromzero的資料夾以後,
應該可以看到fromzero.exe,以及其它的一些檔案及資料夾,
當中就包含了tk和numpy,
顯然pyinstaller自動幫我們評估將函式庫給包進來了!
那麼,在命令提示字元打fromzero.exe,
或者在資料夾中連點兩下就可以執行了……咦?

1
2
3
4
5
6
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’的方式即可)

1
2
3
4
5
6
7
8
9
>>> 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取得變數:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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') # 把剛剛用完的檔案刪掉

我們在重新下一次指令:

1
C:\Users\Desolve\utils>pyinstaller fromzero.py -F -y -w

這時候應該同樣可以在dist資料夾看到fromzero.exe,
且由於我們下了-w,所以執行時後面不會出現命令提示字元了!

如果要進行簡單加密的話,
pyinstaller預設是使用tinyaes:

1
pip install tinyaes

安裝後,打包時額外加上 — key=”(16個字元的字串)”即可,
少掉的字元會補0。
例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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才可以。

那麼,我們就明天見囉!

工商時間:

抽獎活動還在繼續累積人數(現在好像沒有人想抽XD)
Python Taiwan的連結第100篇的文章 底下,
公開分享到你的臉書、按讚該篇文章、並留言告訴我說,
「你最喜歡這一整個系列的哪一篇?為什麼?」或
「除了從LeetCode學演算法系列以外,
你還想要看到關於什麼方向的文章?」

超過20則留言的話 (有完成以上步驟的才算),我們就抽一組
「從Leetcode學演算法|進階篇」+「從Leetcode學演算法|面試篇」
課程的免費兌換券進行贈送!

期限嘛…就延長到滿人數吧XDD (不然也沒辦法哈哈)

容筆者工商一下,
「從Leetcode學演算法|進階篇」 開放預購啦!
這次選了40道難度加深的LeetCode題目,
同樣也會細部解說對應的技巧及須要掌握的演算法!
同時這次購買進階篇的話,
額外還加贈**「從Leetcode學演算法|面試篇」** !
當中包含了面試準備須知分享及訪談國內外不同經驗的工程師
讓你不論是想走前端/後端/一般軟工 或者是想找國外的工作
初學想轉職 還是正在工作 ,都能夠從中得到收穫呦!
有興趣的朋友可以使用下面的早鳥優惠~
「從Leetcode學演算法|進階篇」+「從Leetcode學演算法|面試篇」
https://bit.ly/leetcodeadv

「從Leetcode學演算法」全套(基礎/進階/面試篇)同捆優惠:
https://bit.ly/leetcodeall

共發表了 171 篇文章 ‧ 總計 311.6k
使用 Hugo 建立
主題 StackJimmy 設計