1. 程式人生 > >Python 官方團隊在打包專案中踩過的坑

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 被接受之後,還花了幾乎兩年時間才首次實施就不足為奇了。計劃、測試和實施已經在背後進行了一年多。

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

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 即可。

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】,發表你的意見。

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

小結

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

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

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

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

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