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.orghttps://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進階系列、好書推薦系列、技術寫作、優質英文推薦與翻譯等等,歡迎關注哦。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM