# `zipapp` —-- 管理可執行的 Python zip 封存檔案

在 3.5 版被加入.

原始碼：Lib/zipapp.py

本模組提供工具來建立包含 Python 程式碼的 zip 檔案，這些檔案可以 直接由 Python 直譯器執行。此模組同時提供了 zipapp 命令列介面 和 zipapp Python API。

## 基本範例

以下範例展示了如何使用 zipapp 命令列介面，從一個包含 Python 程式碼的目錄中建立一個可執行的封存檔案。執行時，該封存檔案將會執行封存檔案中 `myapp` 模組的 `main` 函式。

    $ python -m zipapp myapp -m "myapp:main"
    $ python myapp.pyz
    <output from myapp>

## 命令列介面

當從命令列作為程式呼叫時，會使用以下格式：

    $ python -m zipapp source [options]

如果 source 是一個目錄，這將會從 source 的內容建立一個封存檔案。如果 source 是一個檔案，它應該是一個封存檔案，並且它將被複製到目標封存檔案（或者如果指定了 --info 選項，將會顯示其 shebang 行的內容）。

可使用以下選項：

-o <output>, --output=<output>

  將輸出寫入名為 output 的檔案。如果未指定此選項，輸出檔名將與輸入 source 相同，並加上 `.pyz` 副檔名。如果給定了明確的檔名，則會直接使用它（因此如果需要，應包含 `.pyz` 副檔名）。

  如果 source 是一個封存檔案，則必須指定輸出檔名（且在這種情況下，output 不得與 source 相同）。

-p <interpreter>, --python=<interpreter>

  在封存檔案中加入一行 `#!`，指定 interpreter 作為要執行的命令。此外，在 POSIX 上，使封存檔案成為可執行檔。預設是不寫入 `#!` 行，也不讓檔案成為可執行檔。

-m <mainfn>, --main=<mainfn>

  在封存檔案中寫入一個執行 mainfn 的 `__main__.py` 檔案。mainfn 引數應為 "pkg.mod:fn" 的形式，其中 "pkg.mod" 是封存檔案中的一個套件/模組，而 "fn" 是給定模組中的一個可呼叫物件。`__main__.py` 檔案將會執行該可呼叫物件。

  複製封存檔案時，不可指定 `--main`。

-c, --compress

  使用 deflate 方法壓縮檔案，以減少輸出檔案的大小。預設情況下，檔案在封存檔案中是不壓縮儲存的。

  複製封存檔案時，`--compress` 沒有作用。

  在 3.7 版被加入.

--info

  顯示嵌入在封存檔案中的直譯器，以供診斷之用。在這種情況下，任何其他選項都會被忽略，且 SOURCE 必須是一個封存檔案，而不是一個目錄。

-h, --help

  印出簡短的使用訊息並退出。

## Python API

此模組定義了兩個便利的函式：

zipapp.create_archive(source, target=None, interpreter=None, main=None, filter=None, compressed=False)

  從 source 建立一個應用程式封存檔案。source 可以是以下任何一種：

  - 一個目錄的名稱，或是一個指向目錄的 類路徑物件，在這種情況下，將會從該目錄的內容建立一個新的應用程式封存檔案。

  - 一個現有應用程式封存檔案的名稱，或是一個指向該檔案的 類路徑物件，在這種情況下，該檔案會被複製到目標（並修改它以反映為 interpreter 引數所給定的值）。如果需要，檔名應包含 `.pyz` 副檔名。

  - 一個以位元組模式開啟以供讀取的檔案物件。該檔案的內容應該是一個應用程式封存檔案，並且檔案物件被假定位於封存檔案的開頭。

  target 引數決定了產生的封存檔案將被寫入何處：

  - 如果它是一個檔案的名稱，或是一個 類路徑物件，封存檔案將被寫入該檔案。

  - 如果它是一個開啟的檔案物件，封存檔案將被寫入該檔案物件，該物件必須以位元組模式開啟以供寫入。

  - 如果省略了 target（或為 `None`），則 source 必須是一個目錄，而 target 將是一個與 source 同名的檔案，並加上 `.pyz` 副檔名。

  interpreter 引數指定了將用來執行封存檔案的 Python 直譯器名稱。它會作為「shebang」行寫入封存檔案的開頭。在 POSIX 上，這將由作業系統解讀，而在 Windows 上，它將由 Python 啟動器處理。省略 interpreter 將導致不寫入 shebang 行。如果指定了直譯器，且 target 是一個檔名，則目標檔案的可執行位元將被設定。

  main 引數指定了一個可呼叫物件的名稱，它將被用作封存檔案的主程式。只有當 source 是一個目錄，且該 source 尚未包含 `__main__.py` 檔案時，才能指定此引數。main 引數應採用「pkg.module:callable」的形式，封存檔案將透過引入「pkg.module」並執行給定的無引數可呼叫物件來執行。如果 source 是一個目錄且不包含 `__main__.py` 檔案，則省略 main 會導致錯誤，因為這樣產生的封存檔案將無法執行。

  可選的 filter 引數指定了一個回呼函式，該函式會被傳入一個 Path 物件，代表要加入的檔案路徑（相對於 source 目錄）。如果要加入該檔案，它應該回傳 `True`。

  可選的 compressed 引數決定檔案是否被壓縮。如果設定為 `True`，封存檔案中的檔案將使用 deflate 方法進行壓縮；否則，檔案將不壓縮儲存。複製現有封存檔案時，此引數無效。

  如果為 source 或 target 指定了檔案物件，呼叫者有責任在呼叫 create_archive 後將其關閉。

  複製現有封存檔案時，提供的檔案物件只需要 `read` 和 `readline`，或 `write` 方法。從目錄建立封存檔案時，如果 target 是一個檔案物件，它將被傳遞給 `zipfile.ZipFile` 類別，並且必須提供該類別所需的方法。

  在 3.7 版的變更: 新增 filter 與 compressed 參數。

zipapp.get_interpreter(archive)

  回傳封存檔案開頭 `#!` 行中指定的直譯器。如果沒有 `#!` 行，則回傳 `None`。archive 引數可以是一個檔名或一個以位元組模式開啟以供讀取的類檔案物件。它被假定位於封存檔案的開頭。

## 範例

將一個目錄打包成一個封存檔案，並執行它。

    $ python -m zipapp myapp
    $ python myapp.pyz
    <output from myapp>

使用 `create_archive()` 函式也可以達到同樣的效果：

    >>> import zipapp
    >>> zipapp.create_archive('myapp', 'myapp.pyz')

要讓應用程式在 POSIX 上可以直接執行，請指定要使用的直譯器。

    $ python -m zipapp myapp -p "/usr/bin/env python"
    $ ./myapp.pyz
    <output from myapp>

要替換現有封存檔案上的 shebang 行，請使用 `create_archive()` 函式建立一個修改過的封存檔案：

    >>> import zipapp
    >>> zipapp.create_archive('old_archive.pyz', 'new_archive.pyz', '/usr/bin/python3')

要原地 (in place) 更新檔案，請使用 `BytesIO` 物件在記憶體中進行替換，然後再覆寫來源檔案。請注意，原地覆寫檔案存在風險，錯誤可能導致原始檔案遺失。這段程式碼沒有防範此類錯誤，但生產環境的程式碼應作此類防護。此外，此方法只有在封存檔案能容納於記憶體中時才有效：

    >>> import zipapp
    >>> import io
    >>> temp = io.BytesIO()
    >>> zipapp.create_archive('myapp.pyz', temp, '/usr/bin/python2')
    >>> with open('myapp.pyz', 'wb') as f:
    >>>     f.write(temp.getvalue())

## 指定直譯器

請注意，如果你指定了一個直譯器然後分發你的應用程式封存檔案，你需要確保所使用的直譯器是可攜的。Windows 的 Python 啟動器支援大多數常見形式的 POSIX `#!` 行，但還有其他問題需要考慮：

- 如果你使用「/usr/bin/env python」（或「python」命令的其他形式，例如「/usr/bin/python」），你需要考慮到你的使用者可能將 Python 2 或 Python 3 作為預設版本，並編寫你的程式碼使其能在兩個版本下運作。

- 如果你使用一個明確的版本，例如「/usr/bin/env python3」，你的應用程式將無法為沒有該版本的使用者運作。（如果你尚未讓你的程式碼與 Python 2 相容，這可能正是你想要的）。

- 沒有辦法說「python X.Y 或更新版本」，所以要小心使用像「/usr/bin/env python3.4」這樣的確切版本，因為舉例來說，你將需要為 Python 3.5 的使用者更改你的 shebang 行。

通常，你應該使用「/usr/bin/env python2」或「/usr/bin/env python3」，這取決於你的程式碼是為 Python 2 還是 Python 3 編寫的。

## 使用 zipapp 建立獨立應用程式

使用 `zipapp` 模組，可以建立獨立的 Python 程式，這些程式可以分發給終端使用者，他們只需要在他們的系統上安裝合適版本的 Python。實現這一點的關鍵是將應用程式的所有依賴項與應用程式程式碼一起打包到封存檔案中。

建立獨立封存檔案的步驟如下：

1. 像平常一樣在一個目錄中建立你的應用程式，這樣你就有一個 `myapp` 目錄，其中包含一個 `__main__.py` 檔案和任何支援的應用程式程式碼。

2. 使用 pip 將你應用程式的所有依賴項安裝到 `myapp` 目錄中：

       $ python -m pip install -r requirements.txt --target myapp

   （這假設你的專案需求在一個 `requirements.txt` 檔案中——如果沒有，你可以在 pip 命令列上手動列出依賴項）。

3. 使用以下命令打包應用程式：

       $ python -m zipapp -p "interpreter" myapp

這將產生一個獨立的可執行檔，可以在任何有適當直譯器的機器上執行。詳情請參閱 指定直譯器。它可以作為單一檔案交付給使用者。

在 Unix 上，`myapp.pyz` 檔案本身就是可執行的。如果你偏好一個「普通」的命令名稱，可以重新命名檔案以移除 `.pyz` 副檔名。在 Windows 上，`myapp.pyz[w]` 檔案是可執行的，因為 Python 直譯器在安裝時會註冊 `.pyz` 和 `.pyzw` 副檔名。

### 注意事項

如果你的應用程式依賴於一個包含 C 擴充的套件，該套件無法從 zip 檔案中執行（這是一個作業系統的限制，因為可執行程式碼必須存在於檔案系統中，作業系統的載入器才能載入它）。在這種情況下，你可以從 zipfile 中排除該依賴項，並要求你的使用者安裝它，或者將它與你的 zipfile 一起發布，並在你的 `__main__.py` 中加入程式碼，將包含解壓縮後模組的目錄加入到 `sys.path` 中。在這種情況下，你需要確保為你的目標架構提供適當的二進位檔案（並可能在 runtime 根據使用者的機器選擇正確的版本加入到 `sys.path` 中）。

## Python Zip 應用程式封存檔案格式

自 2.6 版以來，Python 就能夠執行包含 `__main__.py` 檔案的 zip 檔案。為了能被 Python 執行，一個應用程式封存檔案只需要是一個標準的 zip 檔案，其中包含一個 `__main__.py` 檔案，該檔案將作為應用程式的進入點執行。如同任何 Python 腳本一樣，腳本的父目錄（在此情況下是 zip 檔案）將被放置在 `sys.path` 上，因此可以從 zip 檔案中引入更多的模組。

zip 檔案格式允許在 zip 檔案前附加任意資料。zip 應用程式格式利用此功能在檔案前附加一個標準的 POSIX「shebang」行（`#!/path/to/interpreter`）。

因此，Python zip 應用程式格式的正式定義如下：

1. 一個可選的 shebang 行，包含字元 `b'#!'`，後跟一個直譯器名稱，然後是一個換行符（`b'\n'`）。直譯器名稱可以是任何作業系統「shebang」處理或 Windows 上的 Python 啟動器可接受的內容。在 Windows 上，直譯器應以 UTF-8 編碼，在 POSIX 上應以 `sys.getfilesystemencoding()` 編碼。

2. 由 `zipfile` 模組產生的標準 zipfile 資料。zipfile 內容*必須*包含一個名為 `__main__.py` 的檔案（該檔案必須位於 zipfile 的「根目錄」中——即，它不能在子目錄中）。zipfile 資料可以被壓縮或不壓縮。

如果應用程式封存檔案有 shebang 行，它在 POSIX 系統上可能會設定可執行位元，以允許其直接執行。

沒有要求必須使用此模組中的工具來建立應用程式封存檔案——此模組是一個便利工具，但以任何方式建立的符合上述格式的封存檔案對 Python 都是可接受的。