01.05 Python官方團隊成員揭祕曾經踩過的坑

花下貓語:這是 packaging 系列的第三篇譯文,該系列是全網關於此話題的最詳盡(水平也很高)的一個系列。原作者是 Python 官方打包團隊成員,是 virtualenv 和 tox 項目的維護者,及 setuptools 和 pip 項目的貢獻者。


英文 | Python packaging - Growing Pains【1】

原作 | BERNAT GABOR

譯者 | 豌豆花下貓


在前兩篇文章中,我介紹了Python 具有的包類型以及包的構建方式,尤其介紹了 PEP-517/518。儘管這些更改主要是為了使打包變得更健壯,但是在實施和發佈時,我們卻遇到了一些問題。這篇文章將介紹一部分,希望可以為大家提供經驗教訓,並提出一些有趣的問題以待將來解決。

查看 PEP-517 和 PEP-518 的改動,可以認為構建後端(亦即 setuptools、flit)幾乎沒有做什麼,只是通過 Python 模塊提供了功能接口。大部分繁重的工作都在構建前端上,它需要生成隔離的 Python,然後以新的方式調用構建後端。如今當我們談論構建前端時,我們的選項主要是 pip 或 poetry(和開發者的 tox)。

這些項目由社區維護,由少數活躍的開發者在空閒時間維護。他們並沒有因此獲得報酬,而且需要謹慎考慮這些工具被使用的多種方式。考慮到這一點,在 PEP 被接受之後,還花了幾乎兩年時間才首次實施就不足為奇了。計劃、測試和實施已經在背後進行了一年多。

但是,儘管做了所有準備工作,不可避免的是,第一版確實破壞了一些軟件包,在大多數情況下,人們做的某些操作使維護人員感到驚訝。讓我們試著瞭解其中一些例子,以及它們是如何被解決的。

Python官方團隊成員揭秘曾經踩過的坑

Mink Mingle攝/Unsplash--準備好出發!

PEP-518

此 PEP 引入了TOML文件格式。 【2】一種專門為了易於讀/寫配置而創建的格式。儘管在build-system部分下介紹了打包配置,但其它工具可以自由地將其配置放在tool:name部分下,因為它們擁有 PyPi 命名空間中的名字。各種工具立即開始利用這一點(例如Towncrier【3】、 black【4】等)。

當pip 18.0(於2018年7月22日發佈) 【5】添加對 PEP-518 包的支持時,使用 pyproject.toml 最早出問題,因為 PEP-518 要求所有帶 pyproject.toml 的軟件包必須指定 build-backend 部分。但是,軟件包事先僅將其用於其它項目的配置文件,由於它們沒有事先指定它,當 pip 碰到這些文件時,就會引發錯誤,提示 pyproject.toml 文件無效。

PEP-517

pip wheel 緩存問題

pip 在 PEP-517 世界中的安裝方式是首先生成一個 wheel,然後將其提取。要進入 PEP-517 世界,必須指定 build-backend 鍵,否則每條聲明都需要退回到使用 setup.py 命令。

當 pip 構建 wheel 時,默認情況下會通過緩存系統完成。這是一種提速機制,為了在多個虛擬環境需要同一個 wheel 時,我們不用對其進行重建,而是重複使用它。PEP-517 wheel 的構建操作也利用了這一機制。

但是,當你禁用緩存時,這就變得很麻煩。因為沒有目標文件夾可用於構建 wheel。所以構建過程將失敗,請參閱附錄的問題。【6】這個問題雖然很早就顯現出來了,但由於大多數 CI 系統都在啟用該選項的情況下運行。僅在一天後,pip 19.0.1 修復了該問題。

pyproject.toml 沒有加入 setuptools 中

事實證明,構建後端實際上要做的工作不僅僅是 PEP-517 中描述的公開其 API。後端還需要確保 pyproject.toml 被附加到已構建的源碼包中,否則用戶計算機上的構建後端將無法使用它。setuptools 1650【7】將為setuptools【8】修復此問題,在早期版本中,只需在 MANIFEST.in 中指定 pyproject.toml 即可。

Python官方團隊成員揭秘曾經踩過的坑

Jorge Zapata攝/Unsplash--什麼?!那永遠不會發生

從 setup.py 中導入構建的包

另一個意外問題是從 setup.py 內導入軟件包時。按照約定,軟件包的版本既作為軟件包的元數據公開(setup.py 中的 setuptools,setup 函數的 version 參數),也在軟件包根目錄的__version__ 變量公開。可以在兩個地方都指定變量的內容,但是要使其保持同步就很麻煩。

一種解決方法:許多程序包將其放在根目錄的 version.py 中,然後同時從 setup.py 和程序包根目錄導入它,像這樣from mypy.version import __version__ as version。這能起作用,因為當有人調用 Python 腳本時,當前的工作目錄會自動被添加到 sys.path 中(因此你可以導入公開在其下的內容)。

但是,這種添加當前工作目錄的行為從來不是強制的,更多的是通過python setup.py sdist 調用構建時,產生的副作用。由於這種行為是副作用(並非保證),因此從 setup.py 導入的所有項目都應在構建開始時,將腳本文件夾顯式地添加到 sys 路徑。

是否該在打包期間(當尚未構建/分發時)導入已編譯的軟件包,這尚有爭議(儘管 Python 打包組傾向於這樣做)。然而,實際上當 setuptools 通過 setuptools.build_meta 暴露其接口時,它選擇不把當前工作目錄添加到系統路徑。

PEP 從未要求後端做此添加,因為大多數構建後端(本質上是聲明式的)根本不需要它。因此,此類功能被認為是前端的責任。setuptools 認為,如果用戶需要此功能,則應在 setup.py 中明確指出,並提前手動在 sys.path 中添加相應的路徑。

為了簡化 pip 代碼庫,pip 決定加入 PEP-517,讓所有人在 setuptools 後端加上 pyproject.toml。現在因為這個問題,即使沒有選擇加入 PEP-517 的程序包也出現崩潰。為了解決這個問題,setuptools 添加了一個新的構建後端(setuptools.build_meta:__ legacy__),當未指定構建後端時,前端可將其用作默認值;當項目添加 build-backend 鍵時,它們還必須更改其 setup.py,要麼將源碼根目錄添加到 sys.path,要麼避免從源碼根目錄導入。

自舉的後端

還出現了另一個有趣的問題,該問題的用戶群更加緊密,但是卻暴露了一個有趣的問題。如果我們不想使用 wheel,我們只能通過源發行版進行設置;我們應該如何解決”如何提供構建後端的構建後端的問題“?例如,setuptools 通過setuptools 打包自身。也即當 setuptools 通過 PEP-517 指定了這一點時,構建前端將被放入無限循環內。

要安裝 pugs 庫,它首先會嘗試創建一個隔離的環境。這個環境需要 setuptools ,因此構建前端就需要構建一個 wheel 來滿足它。wheel 構建本身將觸發隔離環境的創建,該環境又依賴於 setuptools。

如何打破這個循環?要求所有構建後端必須暴露為 wheel?允許後端構建自身?這些自建後端是否應該負擔依賴項?漫長的各種觀點間爭論,利與弊,所以如果你有興趣,請進入python Discourse board【9】,發表你的意見。

Python官方團隊成員揭秘曾經踩過的坑

Sneaky Elbow攝/Unsplash--我們是一夥的

小結

打包是很難的。在業餘時間完善打包系統,使用戶可以在打包期間編寫和運行任意代碼,但還不引起任何破壞,這幾乎是不可能的。

現在有了 PEP-518,構建時依賴項是明確的,並且構建環境易於創建。有了 PEP-517,我們可以使用更具聲明性的打包命名空間,這減少了用戶犯錯的可能,當錯誤不可避免時,也能提供更好的消息。

誠然,在進行這些更改時,某些程序包可能會損壞,並且我們可能令曾經有效的方法失效。但是,我們(PyPa 的維護者)並不是出於惡意而這樣做的,因此,當出現錯誤時,請務必填寫詳細的錯誤報告,例如什麼錯誤、你的使用方法,以及你的用例。

我們努力在真誠地改善打包生態系統,為此我們創建了集成測試【10】存儲庫,以確保將來至少可以捕獲到其中的一些邊緣用例,免得它們落入到你的機器中。如果你對打包有任何建議或訴求,請隨時在“ 討論Python論壇【11】”的打包部分進行討論,或者為相關工具提一個 issue。

Python官方團隊成員揭秘曾經踩過的坑

Milan Popovic攝/ Unsplash--結束了

先到此為止了,謝謝閱讀完!我要感謝Paul Ganssle【12】審閱了打包系列文章,並要感謝Tech At Bloomberg【13】允許我在工作期間作開源貢獻。

相關鏈接

[1] Python packaging - Growing Pains: https://www.bernat.tech/growing-pain/

[2] TOML文件格式。: https://github.com/toml-lang/toml

[3] Towncrier: https://pypi.org/project/towncrier/

[4] black: https://pypi.org/project/black/

[5] pip 18.0(於2018年7月22日發佈): https://pip.pypa.io/en/stable/news/%23id61#id61

[6] 請參閱附錄的問題。: https://github.com/pypa/pip/issues/6158

[7] setuptools 1650: https://github.com/pypa/setuptools/pull/1650

[8] setuptools: https://github.com/pypa/setuptools/pull/1650

[9] python Discourse board: https://discuss.python.org/t/pep-517-backend-bootstrapping

[10] 集成測試: https://github.com/pypa/integration-test

[11] 討論Python論壇: https://discuss.python.org/c/packaging

[12] Paul Ganssle: https://twitter.com/pganssle

[13] Tech At Bloomberg: https://twitter.com/techatbloomberg


主要分享 Python基礎、Python進階、Python哲學、編程語言、書籍推薦等內容,另外還有官方 PEP 翻譯與優質外文的翻譯,值得關注一同學習。


分享到:


相關文章: