歡迎光臨
每天分享高質量文章

setup.py即將下崗,PEP 518 說明繼任者

來源:Python程式員

ID:pythonbuluo

註:PEP = Python Enhancement Proposal (Python增強建議書,即Python開發規範)

摘要

本PEP詳細說明瞭Python軟體包要在選定的構建(Build)系統上運行時,應該如何指定其依賴關係。本規範引入了一個新的配置檔案,用於指定軟體包的構建依賴關係(假定今後的配置會使用相同的配置檔案作為參考)。

基本原理

當Python首次開發用於構建專案、軟體分發的工具時,distutils [1]是選定的解決方案。隨著時間的推移,setuptools [2]越來越流行,它在distutils的基礎上增加了一些功能。兩者都應用了setup.py檔案這樣一個概念。專案維護人員通過執行這個檔案來構建其軟體的發行版(使得用戶也能夠安裝上述發行版)。

distutils是Python標準庫的一部分,所以,使用一個可執行檔案來指定distutils下的構建條件是沒有問題的。將構建工具作為Python的一部分意味著,專案維護人員如果要構建一個專案的發行版,無需擔心setup.py有哪些外部依賴項。唯一的依賴項只是Python,因此沒有必要指定任何依賴信息。

但是當一個專案選擇使用setuptools時,像setup.py這樣的可執行檔案的使用就成了一個問題。你無法在不知道setup.py檔案依賴關係的條件下執行它。可是,目前還沒有標準的方法,在不執行儲存著依賴信息的setup.py檔案的情況下,自動地瞭解它具體有哪些依賴項。這就形成了一個悖論:你不運行這個檔案,你就無法知道它的內容;你不知道這個檔案的內容,就無法運行它。

setuptools嘗試用它的setup() 函式的setup_requires引數來解決這個問題[3]。 此解決方案有許多問題,例如:

  • 除了setuptools本身,沒有工具可以在不執行setup.py的情況下訪問這些信息,但是如果不安裝這些專案,setup.py將無法執行。

  • 儘管setuptools本身會安裝setup.py中列出的一切,但在執行setup() 函式期間,它們將不會被安裝,這意味著實際使用此處添加的任何東西的唯一方法是通過越來越複雜的機制來延遲匯入和使用,直到後來執行setup() 函式為止。

  • 該方案不包括setuptools本身,也不能包括setuptools的替代品,這意味著像numpy.distutils這樣的專案很大程度上無法利用它,專案不能利用較新的setuptools功能,直到用戶自然地將setuptools的版本升級到較新的版本。

  • setup_requires中列出的專案只要執行setup.py就會被安裝,但執行setup.py的常見方式是通過另一個工具,比如已經負責管理依賴關係的pip。 這意味著像pip install spam這樣的命令可能最終導致pip和setuptools下載和安裝軟體包,最終,用戶需要配置這兩個工具(並且不受控制地呼叫setuptools)來更改它安裝的儲存庫等設置。 這也意味著用戶需要瞭解這兩種工具的發現規則,因為每個工具可能支持不同的軟體包格式或以不同方式確定最新版本。

這導致了setup_requires很少被人使用的情況,在這種情況下,專案傾向於只是在多個setup.py檔案之間複製和粘貼代碼片段,或者完全跳過,僅僅只在某個地方記錄好–希望用戶在嘗試建立或安裝他們的專案之前,已經手動安裝好這些內容。

所有這一切使得pip [4]假定在執行setup.py檔案時setuptools是存在的。但問題在於,如果另一個專案像setuptools那樣開始在社區中獲得關註,這個專案就沒有可擴展性。如此一來,會阻止其他專案獲得應有的關註。因為當pip無法推斷出專案需要的是除setuptools以外的某個東西時,使用setuptools便會產生衝突。

本PEP試圖在特定檔案中、以一種宣告式的方式顯式列出專案構建系統的最小依賴關係,從而解決當前的狀況。此舉允許專案列出它必須具有何種構建依賴關係。例如,原始碼簽出到wheel,同時不落入setup.py所形成的悖論中。即,工具無法推斷專案需要自行構建的東西。實施本PEP將允許專案預先指定他們依賴的構建系統,以便像pip這樣的工具可以確保所有依賴條件已經安裝,以便運行構建系統來進行構建。

為了提供更多的背景關係和推動本PEP,可以把所需的(大體)步驟看成是生成一個手工專案的過程:

  1. 專案的原始碼簽出

  2. 構建系統的安裝

  3. 構建系統的運行

本PEP涵蓋了第2步。 預計未來的PEP將包括第3步,包括如何使構建系統動態指定構建系統執行其工作所需的更多依賴性。 但是,本PEP的目的是為構建系統指定要開始運行所需的最低要求。

規範

構建系統的依賴關係將儲存在一個名為pyproject.toml的檔案中,該檔案以TOML格式編寫[6]。選擇這種格式是因為它可供人來使用(不像JSON [7]),它足夠靈活(不像configparser [9])起源於某個標準(也不像configparser [9]),不過於複雜(不像YAML [8])。 TOML格式已被Rust社區用作其包管理器的一部分[14],據私人電郵所述,他們對選擇TOML感到非常滿意。關於為什麼不選擇各種替代品的更詳細的討論可以閱讀以下其他檔案格式的部分。

在配置檔案中將會有一個[build-system]表來儲存與構建相關的資料。最初,表中只有一個關鍵字是有效的和必需的:requires。該鍵將包含一個字串串列的值,代表執行構建系統所需的PEP 508依賴條件(意味著執行setup.py檔案需要哪些依賴條件)。

以下的JSON架構[15]將與資料格式匹配,表示了某個特定型別的結果資料。這些資料來自於僅供演示用的TOML檔案:

對於絕大多數依賴setuptools的Python專案,pyproject.toml檔案會是這個樣子:

目前社區中setuptools和wheel的使用非常廣泛,所以當pyproject.toml檔案不存在時,構建工具將使用上面的示例配置檔案作為它們的預設語意。

除了[tool]表格,所有其他頂級密鑰和表格被保留下來,供其他的PEP將來使用。在[tool]表格中,只要使用了[tool]中的子表,工具就允許用戶指定其配置資料,例如,名為flit的工具會將其配置儲存在[tool.flit]中。

我們需要一些機制來在工具中分配tool.*命名空間中的名稱,以確保不同的專案不會嘗試使用相同的子表產生衝突。 我們的規則是:當且僅當一個專案擁有Cheeseshop / PyPI中的$NAME條目時,它才可以使用子表tool.$NAME。

一些未接受的想法

語意版本號

為了將來驗證配置檔案的結構,最初提出了語意版本號。 預設值是1,背後的想法是:如果發生了針對之前定義的密鑰或表格的語意變化,而這些變化不向後兼容,則語意版本將增加一個新的數字。

但最終卻認定這是一個不成熟的優化。 我們的預期是,在配置檔案中對語意上預先定義的內容的更改將是相當保守的。 在發生向後不兼容的變化的情況下,可以使用不同的名稱作為新的語意,以避免破壞舊的工具。

一個嵌套更深的命名空間

這個PEP的早期草案有一個頂級[Package]表。 想法是為語意版本方案限定範圍(請參閱語意版本關鍵字來瞭解這個想法被拒絕的原因)。 由於不再需要範圍的限定,因此擁有頂級表的重要性變得多餘。

其他表名
[build-system]表的另一個名字是[build]。 替代名稱較短,但並未表達信息儲存在表中的意圖。 經過distutils-sig郵件串列上發起的投票,當前的名稱勝出。

其他檔案格式

提出了其他幾種檔案格式供考慮,都因各種原因而被拒絕。 關鍵要求是該格式可以由人進行編輯,且可以通過專案落地。 這徹底排除了某些格式,如對人類不友好的XML,而且從未認真討論過。

JSON

JSON格式[7]起初納入考慮,但很快被拒絕。 儘管作為基於字串的人類可讀的資料交換格式非常好,但語法本身並不適合人類做簡單的編輯(例如,語法比所需的更冗長而不允許有註釋)。

提議的資料的示例JSON檔案將是:

YAML

YAML格式[8]被設計為JSON的超集[7],同時更易於手工操作。 YAML有三個主要問題。

一個是規範太多:如果打印在letter尺寸的紙上,則為86頁。這就使得有人可能會使用YAML的功能與一個解析器一起工作,而不是另一個解析器。有人建議在一個子集上進行標準化,但這基本上意味著要創建一個特定於該檔案的新標準,這個標準是不容易長期處理的。

二是YAML預設本身並不安全。該規範允許在處理配置資料時最好避免代碼的任意執行。當然可以避免這種行為 – 例如,PyYAML提供了一個safe_load操作 – 但是如果任何工具不小心使用load,那麼它們會自行開啟任意代碼執行。雖然這個PEP專註於構建固有涉及代碼執行的專案,但其他配置資料(如專案名稱和版本號)最終可能會在相同的檔案中隨意執行任意代碼。

最後,最流行的YAML的Python實現是PyYAML [10],它是一個包含幾千行代碼的大型專案,也是一個可選的C擴展模塊。雖然本身並不一定是個問題,但對於像pip這樣的專案來說,這更像是一個問題,因為他們很可能需要將PyYAML作為依賴項供應商,以便完全獨立(否則,最終會導致您安裝需要安裝工具的工具才能正常工作)。 PyYAML的一個概念驗證已經完成,看看供應一個簡單版本的庫是多麼地簡單,這一切表明瞭一種可能性。

一個YAML示例檔案:

Configparser

一個基於configparser INI風格配置檔案考[9]。 不幸的是,沒有關於configparser接受什麼的規範,導致版本之間的支持不一致。 例如,Python 2.7中的ConfigParser接受的內容與Python 3中的configparser接受的內容不同。 雖然可以標準化Python 3接受的內容,並簡單地供應configparser模塊的backport,但這確實意味著此PEP必須編碼,所有專案希望使用configparser的backport才能使用此PEP指定的元資料。 這是過度限制性的,如果有人不知道預期特定版本的configparser會導致混淆。

一個示例INI檔案是:

Python語法

有人提議使用Python語法作為配置格式。 該檔案將在頂層包含一個字典,資料全部在該字典中,並且由鍵定義部分。 所有的Python程式員都會習慣這種格式,而且不需要第三方依賴來讀取配置資料,如果用ast.literal_eval()進行解析,它可能是安全的[13]。 Python語法可以與JSON相同,同時支持尾隨逗號和註釋。 另外,Python的更豐富的資料模型可能對未來的某些配置需求非常有用(例如非字串字典密鑰,浮點數與整數值)。

不過,python語法是Python特有的格式,我們預計這些資料可能需要通過不是用Python編寫的打包工具等來讀取。

提議的Python語法檔案示例如下:

其他檔案名稱

其他幾個檔案名在考慮後並未接受(這是一個非常相似的話題,最終決定主要是根據喜好)。

pysettings.toml

最合理的選擇。

pypa.toml

雖然參考PyPA [11]是有道理的,但這是一個有點用處的術語。沒有特定領域的知識,最好讓檔案名有意義。

pybuild.toml

從這個PEP的限制性角度來看,這個檔案名是有意義的,但是如果有任何非構建元資料被添加到檔案中,那麼名稱就不再有意義了。

pip.toml

工具特定。

meta.toml

太通用;專案可能希望擁有自己的元資料檔案。

setup.toml

在保持setup.py的傳統感謝的同時,它不一定與未來檔案可能包含的內容相匹配(例如,.e.g知道專案名稱是否是其設置的一部分?)。

pymeta.toml

對新手不太明顯。

pypackage.toml&pypackaging.toml

概念混淆,考慮什麼才是一個“包”?(專案和包的區別)

pydevelop.toml

該檔案可能包含非特定於開發的細節。

pysource.toml

與原始碼沒有直接關係。

pytools.toml

由於該檔案(當前)針對專案管理,因此具有誤導性。

dstufft.toml

個體相關性太大

英文原文:https://www.python.org/dev/peps/pep-0518/
譯者:泰然

《Python人工智慧和全棧開發》2018年07月23日即將在北京開課,120天衝擊Python年薪30萬,改變速約~~~~

*宣告:推送內容及圖片來源於網絡,部分內容會有所改動,版權歸原作者所有,如來源信息有誤或侵犯權益,請聯繫我們刪除或授權事宜。

– END –


更多Python好文請點擊【閱讀原文】哦

↓↓↓

赞(0)

分享創造快樂