"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 都是可接受的。
