英文 | Python packaging - Past, Present, Future【1】html
原做 | BERNAT GABOR前端
譯者 | 豌豆花下貓python
聲明 :本文得到原做者受權翻譯,轉載請保留原文出處,請勿用於商業或非法用途。git
你是否想過在運行 pip install 時究竟發生了什麼?這篇文章將給你一個關於過去所涉及的步驟的詳細綜述,以及它是如何隨着 PEP-517 和 PEP-518 的採用而改變的。github
在前一篇文章中,我描述瞭如何作到安裝三種類型的內容:源碼樹(source tree)、源發行版(source distribution)和 wheel。只有最後兩種類型會被上傳到 PyPi 中央存儲倉,但你也能夠得到源碼樹(例如,經過爲 pip 加入 git 協議)。與其它類型相比,wheel 的優勢是不須要在用戶機器上進行任何構建操做;只須要下載和提取。後端
如今能夠獨立出構建的環境(用戶或開發者的機器),但你仍然須要構建包(sdist 或 wheel)。爲了作到這一點,你須要一些適當的構建器。在過去,對第三方包的需求很早就表現出來了。api
遵循內置電池的原則,在 2000 年的 Python 1.6 中,distutils【2】包被添加進 Python 標準庫中。它引入了包含構建邏輯的setup.py
文件的概念,並經過python setup.py
命令觸發。安全
它容許用戶將代碼打包成庫,但沒有聲明(declaration)及自動安裝依賴庫等功能。並且,它的升級週期直接與核心解釋器的發佈週期綁定。函數
setuptools 於 2004 年建立,它構建在 distutils 之上,並擴展了其它優秀的特性。它很快變得很是流行,以致於大多數 Python 安裝包開始將其與核心解釋器一塊兒提供。工具
在那個時候,全部的包都是源發行版。wheel 分發方式出現得很晚,是在 2014 年。distutils 是在只有少數很是精通打包的人的時候建立的。所以它是很是靈活和命令式的(imperative),你寫一個 Python 腳本,能夠修改包生成過程當中的每一步。
但這樣作的缺點是,它一點也不容易學習和理解。隨着 Python 的流行,這開始成爲一個愈來愈嚴重的問題,由於有愈來愈多的用戶對 Python 內部的工做原理不是很精通。
Charles PH 攝/Unsplash--ehhh
關於安裝一個源發行版,pip 主要作了如下工做:
找到這個包
下載源發行版並提取它
在提取的文件夾上運行python setup.py install
(進行構建+安裝)
開發者運行python setup.py sdist
生成分發包,運行python setup.py upload
上傳到中央存儲倉(上傳命令在 2013 年被棄用了,由於有 twine【3】工具,更主要是由於 upload 使用了不安全的 HTTP 鏈接,並且上傳命令會作一次新的構建,也就不容許最終用戶在實際上傳以前檢測(inspect)生成的包)。
當 pip 運行python setup.py install
時,它使用 Python 解釋器來安裝包。所以,構建操做能夠訪問該解釋器中已經存在的全部三方包。最值得注意的是,它徹底使用了安裝在主機 Python 解釋器上的 setuptools 版本。若是一個包使用了 setuptools 的新版本特性,那麼完成安裝的惟一方法就是首先更新已安裝的 setuptools。
若是新版本包含了能破壞其它包的 bug,就會致使出問題。在用戶沒法更改已安裝包的系統上,這尤爲麻煩。當構建器(例如 setuptools)但願使用其它輔助包(例如 cython)時,這也是個問題。
若是缺乏構建器的輔助,一般會拋出導包失敗的錯誤:
File "setup_build.py", line 99, in run from Cython.Build import cythonize ImportError: No module named Cython.Build
在開發者們這邊,沒辦法提供此類構建依賴項。而對於用戶這邊,則須要預先安裝全部的包構建依賴,即便他們不會在運行時使用到。爲了解決這個問題, PEP-518【4】被建立了。
其思想是,與其將主機的 Python 與其當前安裝的構建包一塊兒使用,不如給軟件包提供一種能力,令其清楚地說明其構建操做所需的內容。另外,與其在主機 Python 上提供此功能,咱們是建立了一個獨立的 Python(相似某種虛擬環境)來運行打包。
python setup.py install
如今能夠:
建立一個臨時文件夾
建立一個隔離的(從三方庫的 site packages 中)Python 環境 python -m virtualenv our_build_env
,讓咱們將這個 Python 可執行文件稱爲python_isolated
安裝構建的依賴項
經過python_isolated setup.py bdist_wheel
,生成一個用於安裝的 wheel
提取 wheel 到 Python 的 site packages 文件夾
有了這個,咱們能夠安裝依賴於cython
的包,但沒必要在運行的 Python 環境中實際安裝cython
。指定構建依賴項的文件與方法的是pyproject.toml
元數據文件:
[build-system] requires = [ "setuptools >= 40.8.0", "wheel >= 0.30.0", "cython >= 0.29.4", ]
此外,它還容許打包者指定他們須要的最小版本,而藉助用戶機器上的 pip,能夠輕易地找出這些版本。
當在開發者的機器上生成源發行版或 wheel 時,也可使用相同的機制。當一我的調用pip wheel . --no-deps
命令時,該命令會自動在後臺建立一個包含構建依賴項的獨立 Python,而後在該環境中調用python setup.py bdist_wheel
或python setup.py sdist
命令。
Bruce Galpin攝/Unsplash--yay!
但這裏還有一個問題。請注意,全部這些操做仍然須經過 20 年前引入的機制,即執行setup.py
。整個生態系統仍然構建在 distutils 和 setuptools 的接口基礎之上,因爲試圖保持向後兼容性,無法做太大的變動。
此外,在打包過程當中執行用戶端 Python 代碼是危險的,這可能會致使經驗較少的用戶難以調試的細微錯誤。命令式的(imperative)構建系統在 20 年前對於靈活性來講很是重要,當時咱們還不知道全部的狀況,可是如今咱們已經認識清楚了,極可能能夠爲不一樣的狀況建立出很是健壯和簡單的包構建器。
引用 Paul Ganssle【5】(setuptools 與 dateutil 的維護者)的話:
> 理想狀況下,默認選項應該是一個聲明式的(declarative)構建配置,適用於 99% 的狀況,再提供一個退回到命令式系統的選項,供真正須要靈活性時使用。在這狀況下,若是你發現還須要選擇用命令式的構建,那麼咱們能夠認爲出現了壞味道代碼。
> setup.py
的最大的問題是大多數人是聲明式地使用它,因此當他們用命令式時,每每會將 bug 引入到構建系統。一個這樣的例子:若是你有一個 Python2.7 的依賴項,你可能會試圖有條件地在 setup.py 中指定 sys.version,但 sys.version 僅指的是執行構建的解釋器;相反,你應該對需求項使用聲明式的環境標記…
在 2015 年的引入的flit【6】已經證實了這一假設的正確性。它已經成爲許多 Python 新手最喜歡的打包工具,由於它能夠確保新用戶避免不少這樣的麻煩。然而,要達到這個目的,flit 必須再次構建在 distutils/setuptools 之上,這使得它的實現很是關鍵,而且代碼倉出現至關多的墊片層(例如,它仍然爲源發行版生成 setup.py 文件)。
如今是時候把它從這些束縛中解放出來了,同時也鼓勵其餘人構建本身的打包工具來簡化打包,是時候讓 setup.py 成爲例外而不是默認的了。setuptools 計劃提供【7】一個用戶專用的setup.cfg
接口來起帶頭做用,當一個 PEP-517 系統就位時,在大多數狀況下,你應該選擇它而不是使用 setup.py。
爲了避免把全部東西都綁定到 setuptools 和 distutils 上,並使後端的構建變得便利, PEP-517【8】被建立了。它將構建器分紅後端和前端。前端提供了一個隔離的 Python 環境,知足全部聲明的構建依賴項;後端提供了鉤子,被前端從其隔離環境中調用,以生成源發行版或者 wheel。
此外,咱們再也不經過 setup.py 文件或命令與後端通訊,而是使用了 Python 模塊和函數。全部後端的打包必須提供一個 Python 對象 API,至少實現 build_wheel【9】和 build_sdist【10】兩個方法。該 API 對象是經過 pyproject.toml 文件指定的,使用build-backend
鍵值:
[build-system] requires = ["flit"] build-backend = "flit.api:main"
上述代碼對於前端意味着,你能夠經過在隔離的 Python 環境中運行它來控制後端:
import flit.api backend = flit.api.main # build wheel via backend.build_wheel() # build source distribution via backend.build_sdist()
由後端決定要在哪裏和怎樣公開本身的官方 API:
flit.buildapi
實現setuptools.build_meta
(後面會解釋緣由)poetry.masonry.api
實現由於這些,咱們就擁有了再也不受 distutils 遺留決策約束的打包工具。
Sarthak Dubey攝/Unsplash--更多 yay!
tox 是一個測試工具【14】,大多數項目使用它來確保某個包在多個 Python 解釋器上的版本兼容性。它還能夠輕鬆地建立 Python 環境,在裏面安裝被監測的包,從而更快地復現問題。
爲了可以測試一個包,它首先須要構建一個源發行版。雖然 PEP-518 和 PEP-517 都帶有好的意圖,可是在某些狀況下,啓用它們可能會破壞打包過程。所以,當 tox 在 3.3.0 版本中添加隔離構建時,決定暫時不默認啓用它。你須要手動啓用它(可能會在今年晚些時候——2019 年的版本 4 中默認啓用)。
一旦你指定了一個pyproject.toml
,寫了適當的requires
和build-backend
,你須要啓用tox.ini
中的isolated_build
標誌:
[tox] isolated_build = True
在此以後,在打包過程當中【15】,tox 將在獨立的 Python 環境中爲每一個 PEP-518 提供構建依賴項,來構建源發行版,並調用 PEP-517 所述的構建後端。
若不啓用該功能,tox 將使用老方法構建源發行版,也就是使用安裝了 tox 的解釋器來調用python setup.py sdist
命令。
Matthew Henry攝/Unsplash--這裏沒有免費的午飯呢!
Python 打包官方但願全部這些都是有意義的,並所以擁有一個更用戶友好的、防錯的(error proof )和健壯的構建。這些標準的規範是在 2015 年至 2017 年的長期主題中寫做並爭論出來的。這兩個提案(PEP-517/518)被認爲是足夠好的,能夠得到最大的收益,可是一些不太主流的場景可能會被忽略。
若是你的狀況是被忽略的,不要擔憂,若是咱們認爲必要的話,PEP 在任什麼時候候都是擁抱改進意見的。在本系列的下一篇文章中【16】,我將討論社區在發佈這兩個 PEP 時碰撞到的一些痛點。這些都是咱們應該吸收的教訓,而且代表着咱們仍有一些工做要作。還不是一切都完美,但咱們正在變得更好。若是你能夠幫幫忙,就加入打包社區吧,讓咱們一塊兒把事情作得更好!
在前一篇文章中,source distribution 被譯成「源碼分發」,但它還有一個更被人採用的譯法是「源發行版」,爲了便於接受,因此本文已做修改。翻譯匆忙,若有錯誤,歡迎讀者指正!萬分感謝!PS:後續如有修正,會在知乎專欄修改,請關注「Python進階之旅」:https://zhuanlan.zhihu.com/pythonCat
[1] Python packaging - Past, Present, Future: https://www.bernat.tech/pep-517-518/
[2] distutils: https://packaging.python.org/key_projects/#distutils
[3] twine: https://pypi.org/project/twine/
[4] PEP-518 : https://www.python.org/dev/peps/pep-0518/
[5] Paul Ganssle: https://twitter.com/pganssle
[6] flit: https://pypi.org/project/flit/
[7] 計劃提供: https://github.com/pypa/setuptools/pull/1675
[8] PEP-517: https://www.python.org/dev/peps/pep-0517/
[9] build_wheel: https://www.python.org/dev/peps/pep-0517/#build-wheel
[10] build_sdist: https://www.python.org/dev/peps/pep-0517/#id9
[11] flit: https://flit.readthedocs.io/en/latest/
[12] setuptools: https://setuptools.readthedocs.io/en/latest/history.html#v40-8-0
[13] poetry: https://poetry.eustace.io/docs/pyproject/#poetry-and-pep-517
[14] 測試工具: https://tox.readthedocs.io/en/latest/
[15] 打包過程當中: https://tox.readthedocs.io/en/latest/#system-overview
[16] 下一篇文章中: https://www.bernat.tech/growing-pain/
公衆號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫做、優質英文推薦與翻譯等等,歡迎關注哦。