1. 程式人生 > >Python 打包的現狀:包的三種類型

Python 打包的現狀:包的三種類型

英文 | The state of Python Packaging【1】

原作 | BERNAT GABOR

譯者 | 豌豆花下貓

宣告 :本文獲得原作者授權翻譯,轉載請保留原文出處,請勿用於商業或非法用途。

pip 19.0 已經於 2019 年 1 月 22 日釋出。在其功能列表中,最值得注意的是它現在支援 PEP-517,預設情況下是支援的,如果專案的根目錄中有一個 pyproject.toml。該 PEP 於 2015 年建立,並於 2017 年被接受。儘管 pip 花了一段時間才實現它,但該版本及其後續問題卻表明,很多人根本不熟悉它。

如果你想了解 Python 打包(packaging)生態的現狀及將來如何演變,請繼續閱讀。我們希望,即使上述提到的 Python 增強提案(譯註:即 PEP,關於 PEP 的介紹,請閱讀這篇文章),如今可能會引起一些不愉快,但從長遠來看,我們將從中受益。

我大約在三年前加入了 Python 開源社群(儘管使用它已有 8 年之久)。從早期開始,我就聽說 Python 打包有一點黑匣子的名聲。它有很多未知的內容,人們通常只複製其它專案的構建配置檔案,就使用上了。

在嘗試更好地理解這個黑匣子,並對其進行改進的過程中,我已經成為了 virtualenv 和 tox 專案的維護者,偶爾也為 setuptools 和 pip 做些貢獻。

我希望對這個主題進行詳盡的(並希望是一個較高水平的)論述,並決定將其分為三個部分。在這第一篇文章中,我將對 Python 打包的工作方式及其所具有的打包型別進行大概介紹。在第二篇文章中,我將詳細地介紹軟體包的安裝方式,以及 PEP-517/518 是如何嘗試對其進行改進的。最後,我再專門寫另一篇文章,以介紹在引入這些改進時,我們吸取的一些痛苦的教訓。

事先宣告,我將主要關注 Python 官方的打包系統(即 pip、setuptools,因此沒有 conda 或特定於作業系統的打包程式)。

Marcus Cramer 攝/Unsplash--人們第一次凝視 Python 打包時的臉

一個示例專案

為了講這個故事,我需要先講講如何分發 Python 軟體包的故事;更具體地說,包的安裝在過去是如何運作的,以及我們希望它在將來如何運作。

為了有一個具體的示例,讓我介紹一下我的很棒的示例庫:pugs 。這個庫相當簡單:它只生成一個名為 pugs 的包,僅包含一個名為 logic 的模組。關於 pugs,你猜對了,logic 被用於生成隨機的引號。這是一個展現為原始碼樹(source tree)的簡單示例結構(可以在gaborbernat / pugs 【2】裡獲得):

pugs-project
├── README.rst
├── setup.cfg
├── setup.py
├── LICENSE.txt
├── src
│   └── pugs
│       ├── __init__.py
│       └── logic.py
├── tests
│   ├── test_init.py
│   └── test_logic.py
├── tox.ini
└── azure-pipelines.yml

這裡有四類獨特的內容:

我們的pugs 包在使用者機器的直譯器上能用,意味著什麼?在理想情況下,一旦啟動直譯器,使用者應該能夠 import 它,並呼叫其中的函式:

  • 業務邏輯程式碼(src 資料夾中的內容)

  • 測試程式碼(tests 資料夾和 tox.ini)

  • 包程式碼和元資料(setup.py、setup.cfg、LICENSE.txt、README.rst--請注意,我們如今使用的是事實上的標準打包工具setuptools【3】)

  • 有助於專案管理和維護的檔案:

    • 持續整合(azure-pipelines.yml)
    • 版本控制(.git)
    • 專案管理(例如潛在的 .github 資料夾)
Python 3.7.2 (v3.7.2:9a3ffc0492, Dec 24 2018, 02:44:43)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pugs
>>> pugs.do_tell()
"An enlightened pug knows how to make the best of whatever he has to work with - A Pug's Guide to Dating -  Gemma Correll"

Ryan Antooa 攝/Unsplash--讓我們開始吧,興奮!

Python 包的可用性

Python 怎麼知道什麼可用或不可用?簡短的答案是,它不知道。至少不在前期知道。相反,它將嘗試載入,並動態地檢查是否可用。

它從哪裡載入?有許多可能的位置,但是在大多數情況下,我們說的是從檔案系統的資料夾中載入。這個資料夾在哪裡呢?對於給定的模組,可以列印該模組的表示(representation)來找出:

>>> import pugs
>>> pugs
<module 'pugs' from '/Users/bernat/Library/Python/3.7/lib/python/site-packages/pugs/__init__.py'>

你會發現資料夾的位置取決於:

  • 軟體包的型別(三方庫或者標準庫的內建/aka部分)
  • 它是全域性的或僅限於當前的使用者(請參閱PEP-370【4】)
  • 以及它是系統 Python 還是一個虛擬環境

但是一般來說,對於給定的 Python 直譯器,可以通過打印出 sys.path 變數的內容,來找到可能的目錄列表,例如在我的 MacOS 上:

>>> import sys
>>> print('\n'.join(sys.path))
/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload
/Users/bernat/Library/Python/3.7/lib/python/site-packages
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages

對於第三方軟體包,會是一些 site-packages 資料夾。在以上示例中,請注意哪些是在整個系統範圍內,哪些僅屬於一個特定的使用者。這些包是如何被放在此資料夾中的?它一定是由某些安裝程式放在那裡的。

下圖展示了大多數的執行情況:

  1. 開發者在資料夾(稱為原始碼樹)內編寫一些 Python 程式碼。
  2. 然後,某些工具(例如 setuptools)將原始碼樹打包以進行重新分發。
  3. 生成的軟體包通過另一個工具(twine),上傳到可以被終端使用者計算機訪問的中央儲存倉(通常為https://pypi.org【5】)。
  4. 終端使用者計算機使用一些安裝程式來查詢、下載和安裝相關軟體包。安裝操作最終是在 site-packages 資料夾內,建立正確的目錄結構和元資料。

Pinho/攝--在探索新鮮事物

Python 包的型別

在安裝時,軟體包必須生成至少兩種型別的內容,以放入 site-packages 中:有關軟體包內容的元資料資料夾,其中包含 {package}-{version} .dist-info 和業務邏輯檔案。

/Users/bgabor8/Library/Python/3.7/lib/python/site-packages/pugs
├── __init__.py
├── __pycache__
│   ├── __init__.cpython-37.pyc
│   └── logic.cpython-37.pyc
└── logic.py

/Users/bgabor8/Library/Python/3.7/lib/python/site-packages/pugs-0.0.1.dist-info
├── INSTALLER
├── LICENSE.txt
├── METADATA
├── RECORD
├── WHEEL
├── top_level.txt
└── zip-safe

發行資訊(dist-info)資料夾描述了該軟體包:用於安裝該軟體包的安裝程式、該軟體包所附的許可證、在安裝過程中建立的檔案、頂層 Python 軟體包是什麼、該軟體包暴露的入口等等。在PEP-427【6】 中可以找到每個檔案的詳細說明。

我們如何從原始碼樹中獲得這兩種型別的內容呢?我們面前有兩條截然不同的路徑:

  1. 從我們的原始碼樹生成此目錄結構和元資料,將其壓縮為單個檔案,然後將其釋出到中央軟體包儲存倉。在這種情況下,安裝程式必須下載軟體包並將其解壓到 site-packages 資料夾中。我們將這種型別的包稱為 wheel 包。
  2. 或者,你可以建立一個包含軟體包原始碼的歸檔檔案,構建所需的指令碼和元資料,以生成可安裝的(installable)目錄結構,然後將其上傳到中央儲存倉。這稱為原始碼分發或 sdist。在這種情況下,安裝程式還有很多工作要做,它需要解壓歸檔檔案,執行構建器,然後再將其複製。

這兩個方法的區別主要在於包的編譯/構建操作發生在哪裡:在開發者的計算機上還是在終端使用者的計算機上。如果它發生在開發者的一邊(例如在 wheel 的情況下),則安裝過程非常輕巧。一切都已經在開發機器上完成了。使用者機器的操作僅是簡單的下載和解壓。

在本例中,我們使用 setuptools 作為構建器(從原始碼樹生成要放入 site-packages 資料夾中的內容)。因此,為了在使用者機器上執行構建操作,我們需要確保在使用者機器上有合適版本的 setuptools (如果你使用的是 40.6.0 版的功能,則必須確保使用者具有該版本或大於該版本)。

要考慮的另一種情況是 Python 提供了從其內部訪問 C/C++ 庫的能力(在需要的地方獲得額外的效能)。這樣的軟體包被稱為 C 擴充套件包(C-extension packages),因為它們利用了 CPython 提供的 C 擴充套件 API。

此類擴充套件需要編譯 C/C++ 功能,才能適用與其互動的 C/C++ 庫和當前 Python 直譯器的 C-API 庫。在這些情況下,構建操作實際上涉及到呼叫一個二進位制編譯器,而不僅僅是像純 Python 包(例如我們的 pugs 庫)那樣,生成元資料和資料夾結構。

如果在使用者計算機上進行構建,則需要確保在構建時,有可用的正確的庫和編譯器。現在這是一項相對困難的工作,因為有些特定於平臺的二進位制檔案,也是通過平臺打包工具分發的。這些庫的缺失或版本不匹配通常會在構建時觸發隱祕的錯誤,使使用者感到沮喪和困惑。

因此,如果可能的話,始終選擇將 package 打包成 wheel。這將完全避免使用者缺少正確的構建依賴項的問題(純 Python 型別如 setuptools 或二進位制型別的 C/C++ 編譯器)。即使這些構建依賴項易於配置(例如,使用純 Python 構建器--例如 setuptools),你完全可以避免此步驟,來節省安裝的時間。

話雖如此,仍然有兩種需要提供原始碼分發的情況(即使在你提供 wheel 的情況下):

  1. C 擴充套件的原始碼分發往往更易於稽核,因為人們可以閱讀原始碼,從而在其內容上有更高的透明度:許多大型公司的環境出於此單一原因,更傾向於使用 wheel(它們通常會將此擴充套件到純 Python wheel,主要是為了避免對哪些是純 Python 和什麼不是做分類)。
  2. 你可能無法為每個可能的平臺都提供一個 wheel(在使用 C 擴充套件包的情況下,尤其如此),在這種情況下,原始碼分發可以讓這些平臺自行生成 wheel。

小結

原始碼樹(source tree)、原始碼分發(source distribution)和 wheel 之間的區別:

  • 原始碼樹——包含在開發者的機器/儲存倉上可用的所有專案檔案(業務邏輯、測試、打包資料、CI 檔案、IDE 檔案、SVC 等),例如,請參見上面的示例專案。
  • 原始碼分發——包含構建 wheel 所需的程式碼檔案(業務邏輯+打包資料+通常還包括單元測試檔案,用於校驗構建;但是不包含開發者環境的內容,例如 CI/IDE/版本控制檔案),格式:pugs-0.0 .1.tar.gz 。
  • wheel——包含包的元資料和原始碼檔案,被放到 site packages 資料夾,格式:pugs-0.0.1-py2.py3-NONE-any.whl 。

Charles PH 攝/Unsplash--hmmm

可在此閱讀本系列的下一篇文章【7】,瞭解在安裝軟體包時會發生什麼。謝謝閱讀!

相關連結

[1] The state of Python Packaging: https://www.bernat.tech/pep-517-and-python-packaging/

[2] gaborbernat / pugs: https://github.com/gaborbernat/pugs

[3] setuptools: https://pypi.org/project/setuptools

[4] PEP-370: https://www.python.org/dev/peps/pep-0370/

[5] https://pypi.org: https://pypi.org/

[6] PEP-427: https://www.python.org/dev/peps/pep-0427/%23id14#id14

[7] 下一篇文章: https://www.bernat.tech/pep-517-518/

公眾號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫作、優質英文推薦與翻譯等等,歡迎關注哦