Python zipapp打包教學(超級詳細)

2020-07-16 10:05:02
經過複雜的開發、偵錯之後,終於得到一個 Python 程式,這個程式或許精巧,或許有些古拙,但它是我們心血的結晶,我們當然希望將這個程式發佈出來。

Python 提供了一個 zipapp 模組,通過該模組可以將一個 Python 模組(可能包含很多個源程式)打包成一個 Python 應用,甚至發布成一個 Windows 的可執行程式。

生成可執行的Python檔案包

zipapp 是一個可以直接執行的模組,該模組用於將單個 Python 檔案或整個目錄下的所有檔案打包成可執行的檔案包。

zipapp 模組的命令列語法如下:

python -m zipapp source [options]

在上面命令中,source 引數代表要打包的 Python 源程式或目錄,該引數既可以是單個的 Python 檔案,也可以是資料夾。如果 source 引數是資料夾,那麼 zipapp 模組會打包該資料夾中的所有 Python 檔案。

該命令的 options 支援如下選項:
  • -o <output>,--output=<output>:應選項指定輸出檔案包的檔名。如果不指定該選項,所生成的檔案包的檔名預設為 source 引數值,並加上 .pyz 字尾。
  • -p <interpreter>,--python=<interpreter>:改選項用於指定 Python 直譯器。
  • -m <mainfn>,--main=<mainfn>:該選項用於指定 Python 程式的入口函數。該選項應該為 pkg.mod:fn 形式,其中 pkg.mod 是一個檔案包中的包或模組,fn 是指定模組中的函數。如果不指定該選項,則預設從模組中的 __main__.py 檔案開始執行。
  • -c,--compress:從 Python 3.7 開始支援該選項。該選項用於指定是否對檔案包進行壓縮來減小檔案的大小,預設是不壓縮。
  • --info:該選項用於在診斷時顯示檔案包中的直譯器。
  • -h,--help:該選項用於顯示 zipapp 模組的幫助資訊。

下面在某目錄下建立一個 app 子目錄,該子目錄用於包含多個 Python 程式。首先在該目錄下開發一個 say_hello.py 程式:
def say_hello(name):
    return name + ",您好!"
然後在該目錄下開發一個 app.py 程式來使用 say_hello 模組:
from say_hello import *

def main():
    print('程式開始執行')
    print(say_hello('孫悟空'))
在命令列工具中進入該目錄(app 目錄的父目錄),然後執行如下命令:

python -m zipapp app -o first.pyz -m "app:main"

上面命令指定將當前目錄下的 app 子目錄下的所有 Python 原始檔打包成一個檔案包,並通過 -o 選項指定所生成的檔案包的檔名為 first.pyz;-m 選項指定使用 app.py 模組中的 main 函數作為程式入口。

執行上面命令,將會生成一個 first.pyz 檔案。接下來可以使用 python 命令來執行 first.pyz 檔案:

python first.pyz
程式開始執行
孫悟空,您好!

通過命令列工具在 app 目錄的父目錄下執行如下命令:

python -m zipapp app -m "app:main"

上面命令沒有指定 -o 選項,這意味著該命令將會使用預設的輸出檔名:source 引數值加 .pyz 字尾。執行上面命令,將會在當前目錄下生成一個 app.pyz 檔案。

zipapp建立獨立應用

通過上面介紹的方式打包得到的檔案包中只有當前專案的 Python 檔案,如果 Python 應用還需要使用第三方模組和包(比如前面介紹的需要連線 MySQL 的應用),那麼僅打包該應用的 Python 程式是不夠的。

為了建立能獨立啟動的應用(自帶依賴模組和包),需要執行兩步操作:
  1. 將應用依賴的模組和包下載到應用目錄中。
  2. 使用 zipapp 將應用和依賴模組一起打包成檔案包。

下面在 app 所在目錄下再建立一個 dbapp 子目錄,該子目錄將會作為本應用的目錄。接下來在 dbapp 目錄下新建一個 __main__.py 檔案作為程式入口,這樣程式在打包檔案包時就不需要指定程式入口了。

下面是 __main__.py 檔案的程式碼:
from exec_select import *

# 執行query_db()函數
query_db()
其中,exec_select.py 檔案需要自己新增,包含程式碼如下:
# 匯入存取MySQL的模組
import mysql.connector

def query_db():
    # ①、連線資料庫
    conn = conn = mysql.connector.connect(user='root', password='32147',
        host='localhost', port='3306',
        database='python', use_unicode=True)
    # ②、獲取游標
    c = conn.cursor()
    # ③、呼叫執行select語句查詢資料
    c.execute('select * from user_tb where user_id > %s', (2,))
    # 通過游標的description屬性獲取列資訊
    for col in (c.description):
        print(col[0], end='t')
    print('n--------------------------------')
    # 直接使用for迴圈來遍歷遊標中的結果集
    for row in c:
        print(row)
        print(row[1] + '-->' + row[2])
    # ④、關閉游標
    c.close()
    # ⑤、關閉連線
    conn.close()
最後按照如下步驟將 dbapp 子目錄下的應用打包成獨立應用:
  1. 通過命令列工具在dbapp當前所在目錄執行如下命令:

    python -m pip install -r requirements.txt --target dbapp

    上面命令實際上就是使用 pip 模組來安裝模組,其中 python -m pip install 表示要安裝模組。--target 選項指定將模組安裝到指定目錄下,此處指定將依賴模組安裝到 dbapp 子目錄下。-r 選項指定要安裝哪些模組,此處使用 requirements.txt 檔案列出要安裝的模組和包。-r 選項支援兩個值:
    • 直接指定要安裝的模組或包。
    • 使用清單檔案指定要安裝的模組和包。

    當應用依賴的模組較多時,建議使用清單檔案來列出所依賴的模組。

    如果直接執行上面命令,pip 模組會提示找不到 requirements.txt 檔案,因此需要在當前目錄下新增一個 requirements.txt 檔案,並在該檔案中增加如下一行。

    mysql-connector-python

    如果專案需要依賴多個模組,則可以在 requirements.txt 檔案中定義多行,每行定義一個模組。

    重新執行上面命令,將可以看到 pip 開始下載 mysql-connector-python 模組,下載完成後將可以在 dbapp 子目錄下看到大量有關 mysql-connector-python 模組的檔案。
  2. 如果 pip 在 dbapp 子目錄下生成了 .dist-info 目錄,則建議刪除該目錄。
  3. 使用 zipapp 模組執行打包操作。由於本例的 dbapp 子目錄下包含了 __main__.py 檔案,該檔案將會作為程式入口,因此打包時不需要指定 -m 選項。使用如下命令來打包:

    python -m zipapp dbapp

    與上一節所使用的命令相比,該命令沒有使用 -m 邊項來指定程式入口,該程式將會使用檔案包中的 __main__.py 檔案作為程式入口。執行上面命令,將會得到一個大約為 18MB 的檔案包。因為該檔案包自包含了 mysql-connector-python 模組,所以比較大。

在建立了獨立應用之後,只要目標機器上安裝了合適版本的 Python 直譯器,即可執行該獨立應用。我們可以先使用如下命令解除安裝在 Python 目錄下安裝的 mysql-connector-python 模組:

pip uninstall mysql-connector-python

此時在本機的 Python 目錄下不再包含 mysql-connector-python 模組,但 dbapp.pyz 程式依然可以正常執行,因為它自包含了 mysql-connector-python 模組。