by Eugene Frost on Behanceby Eugene Frost on Behance

相關文章:Python 開發:Ruff Linter、Formatter 介紹 + 設定教學

2024/01/16:刪除部分內容,使文章更緊湊、好讀。

在〈Python 套件管理器——Poetry 完全入門指南〉中,我提到了自己選擇 Poetry 而非 Pipenv 的兩大理由之一,是因為 Poetry 採用了pyproject.toml作為它的工具設定檔

本文則直接以 pyproject.toml 為主角,用一半以上的篇幅對其做進一步介紹。

第二部分則大概講解一下:在 VS Code 中如何透過pyproject.toml,作為 Black、yapf、isort 這幾個常用的 Python 開發工具的共同設定檔,而非使用 VS Code 自身的settings.json來儲存設定。並提醒一些使用上的細節。


pyproject.toml 介紹

容我稍為引述一下前文內容:

pyproject.toml 是 PEP 518 所提出的新標準,原意是作為套件打包設定的標準格式,後來又有了 PEP 621,將其擴充定性為 Python 生態系工具的共同設定檔標準,現在已經被愈來愈多套件所支援,詳細可參考這個清單及頁面中的說明。

作為一個平常沒有在開發 Python 套件的 Web 開發者,關於 pyproject.toml 在套件打包方面的功用,較無涉獵,故本文將聚焦在它作為「工具設定檔」的部分。

至於想用 pyproject.toml 進行套件打包的讀者,可參考同一作者的下列兩篇文章:

  1. A Python Package Developer’s Cheat Sheet
  2. A pyproject.toml Developer’s Cheat Sheet

為了撰寫本文,我大致重看了一下這兩個 PEP 的內容,發現 PEP 518 對於說明與了解 pyproject.toml 的重要性,還是相對高許多。

核心內涵:PEP 518

PEP 518 於 2016 年 5 月提出,對於想多加知曉 pyproject.toml 的人可謂必讀,在此僅摘錄其中三個重點,讓讀者有一個基本理解。

一、為何選擇 TOML 格式?

This format was chosen as it is human-usable (unlike JSON), it is flexible enough (unlike configparser), stems from a standard (also unlike configparser), and it is not overly complex (unlike YAML).

短短幾句話,就已經道盡了選用 TOML 文件格式最大的優勢與理由(對應上述粗體字部分):

  1. 語法易讀易用——相較於 JSON。
  2. 格式彈性——相較於 configparser(Python 標準函式庫)。
  3. 遵循一定標準——也是相較於 configparser。
  4. 不會過度複雜——相較於 YAML。

The TOML format is already in use by the Rust community as part of their Cargo package manager and in private email stated they have been quite happy with their choice of TOML.

當你開始感到認同之際,則進一步說服表示:被稱為最佳 package manger 的 cargo(Rust 語言),就使用了 TOML 格式的設定檔,而且這個選擇受到一定認可。

二、考慮了哪些格式選項?

總共有四種:

  1. TOML
  2. YAML
  3. JSON
  4. CFG/INI

基本上都是常見的設定檔格式,不同格式間的詳細比較可參考這裡。而最終他們選擇了 TOML。

三、用來儲存套件(工具)設定的——tool table

The [tool] table is where any tool related to your Python project, not just build tools, can have users specify configuration data as long as they use a sub-table within [tool], e.g. the flit tool would store its configuration in [tool.flit].

由此可知,pyproject.toml 打從一誕生,就能夠被用作為 Python 專案所使用套件(工具)的設定檔(前提是工具本身必須支援)。

標準做法是使用[tool.<tool_name>]欄位,比如例子中的[tool.flit]和我們已熟悉的[tool.poetry]

正好例子的這兩者都是和打包相關的套件,會優先採用 PEP 518 建議作為打包設定標準的 pyproject.toml,並不難理解。


pyproject.toml 的崛起

在 PEP 518 被提出後,陸續有工具——比如上述的 FlitPoetry——在套件的誕生之初,就決定選用 pyproject.toml 作為獨佔的工具設定檔格式,可謂非常捧場。不用說,這類工具、套件都屬於比較晚建立的專案。

而在 PEP 518 發表就已經被開發出的套件,比如pytestyapf,自然已有自己支援的設定檔格式,也沒有非得再多「支援」pyproject.toml 的必要。

另一方面,從一般開發者的角度看,使用了這兩個工具的開源專案,也一定早就有了原先的工具設定檔,可能是setup.cfgini格式。對於專案開發人員,看不到足夠誘因促使開發者一定要改「採用」pyproject.toml 來儲存工具的設定。

換句話說,早期的 pyproject.toml,對大部分開發者而言,都處於一個可有可無的尷尬狀態

所以,不難想見,pyproject.toml 在誕生後的兩年間,使用率並不高,能見度一直很難突破「打包圈」——直到「它」的出現。


Black Formatter

就在 PEP 518 發表的兩年後,pyproject.toml 能見度開始明顯提升,而孕育出這個改變的主要推手,就是 Black

在《Python Testing with pytest, Second Edition》(入門 pytest 的佳作,可惜目前沒有中譯本,未來應該也不會有😅)一書中,便如此寫道:

In 2018, a Python code formatter named Black started to gain popularity. The only way to configure Black is to use pyproject.toml. Since then, more and more tools have started to support storing configuration in pyproject.toml, including pytest.

可以想像,Flit 或 Poetry,在2018 年以前,使用人口畢竟不多,對 pyproject.toml 的普及自然起不到什麼推波助瀾的效果,但 Black 就不同了。

一個中大型專案,基於協作一致性要求,少不了 formatter 的支持,而主流的選擇,如同〈VS Code 設定 Python Linter、Formatter 教學〉所言,不外乎這三種:

加上 Black 所標榜的「不設定就是最好的設定」,強調格式化結果的一致性,僅能進行極小幅度的客製,對大型專案或是我這種規範控,確實有著相當吸引。

一旦專案使用了 Black 作為 formatter,如前所述,就必須使用 pyproject.toml。而當愈多專案中存在 pyproject.toml,其餘的開發工具也自然更有動力去支援它;有愈多工具支援,專案開發者也更有意願把其餘工具的設定,嘗試遷移到 pyproject.toml。

說起來簡單,但這個「第一步」總是最難發生的。所以,Black 相較於 Flit 或 Poetry,對 pyproject.toml 的普及與推廣作用,更是起到了決定性的作用

不得不感嘆:偉哉 Black!

相閱文章:《Python 功力提升的樂趣》筆記(一)Black、命名、壞味道


awesome-pyproject

GitHub 上有著各種 awesome-xxx 系列的倉庫,收集了 xxx 主題相關或具有代表性的內容,比如 awesome-python

而 pyproject.toml 也有屬於它的 awesome-pyproject——羅列了目前所有能「支援」pyproject.toml 作為工具設定檔的套件清單。想發揮 pyproject.toml 的最大整合能力,有必要看一看這份清單。而本文提及的套件,自然都清單中。

最後還列出了,正在討論是否支援 pyproject.toml 設定的套件,比如 Flake8

遺珠之憾:Flake8

很遺憾目前還不能透過 pyproject.toml 設定 Flake8,只有 Flake9 這個非官方的替代方案:

Flake8 fork that supports reading config from pyproject.toml files.

如果真的很想整合也是可以考慮。


使用 pyproject.toml 設定 Black、yapf、isort

進入本文第二部分,不過首先要強調,這部分的重點並不是設定檔的內容,因為常用的設定三、兩句就講完了。

而會著眼於:如果我要尋找完整的設定教學文件,要去哪裡找?(也可能沒有啦!)

真別說,這不是一個簡單的、一望即知的命題。

原因很簡單,除了 Black 天生就支援 pyproject.toml 外,其餘兩個工具早在 PEP 518 之前就存在了,原生上絕不可能使用 pyproject.toml 作為設定檔。

若要支援 pyproject.toml,就得遵循適用於 pyproject.toml 的格式與選項

因此,上述工具於 pyproject.toml 的設定格式為何、有哪些選項,至少要有一些文件能參考才行。下面會列出我找到的結果。

並非 VS Code 專屬

此外,還需要辨明一下,這些工具在 pyproject.toml 中的設定,可以用在 VS Code 開發(VS Code 會去自動讀取並套用),也可以用在別的地方,比如 CI 工具。

透過 VS Code settings.json 的 Args 設定 formatter

這類做法(如下)已於〈VS Code 設定 Python Linter、Formatter 教學〉中介紹過,可直接點擊參考,本文不再贅述。效果和使用 pyproject.toml 基本相同,但可以想見,只對 VS Code 有效

1
2
3
4
"python.formatting.yapfArgs": [
"-p",
"--style={column_limit=100}",
],

此外,敏銳的你應該已經想到:這兩種設定應該有優先順序

實測後,在兩者都設定時,適用順序為settings.json優先!這點要特別注意。

個人覺得這樣的優先策略實在令人不解——常理而言應該要小範圍(pyproject.toml)的設定更優先適用才對,比如 git 的.gitconfig


設定檔內容與參考文件

要先理解一個重要概念是:這些所謂「設定」,本質上都是這類工具以命令列執行時的「參數」,只是參數如果很多,或每次執行都是一樣的內容,那乾脆寫成一個檔案,讓工具在執行時直接去讀取就好了。

而這類的檔案,通稱為設定檔(configuration file)

設定檔的項目,通常以 key / value 形式組成。每一個 key 的名稱,也通常和原來使用的命令列參數名稱相同,只是格式上可能會略有調整。

比如長參數(verbose parameter)的命名往往以--開頭,而在設定檔中顯然無此必要,可以加以省略。下方的--skip-string-normalization即為一例。

Black

作為原生且僅支援 pyproject.toml 的套件,沒有所謂的遷移問題。不過 Black 可以設定、客製的選項不多,這是我唯二常用的:

1
2
3
[tool.black]
line-length = 100 # 預設值是 88
skip-string-normalization = true # 不強制把單引號變成雙引號

上述設定檔內容,如果還原為 CLI 參數格式,如下:

1
black --line-length=100 --skip-string-normalization

具體設定選項可參考文件

yapf

yapf比較沒有特定的設定檔格式(不像pytest有常見的pytest.ini),使用下列幾樣都可以,且有一定的適用優先順序,以 formatting style 為例,依序為:

  1. Command line 參數。
  2. .style.yapf設定檔。
  3. setup.cfg設定檔。
  4. pyproject.toml設定檔。
  5. ~/.config/yapf/style設定檔。

回到 pyproject.toml,我常用的設定配置如下:

1
2
[tool.yapf]
column_limit = 100

對,就這樣。三者我主要都只調整「單行字元上限」這一項而已。若是個人開發,直接設定於 VS Code 的settings.json也無妨,不一定要寫在 pyproject.toml。

但如果是用於團隊協作,就有使用設定檔的必要。

設定項目文件

好像沒有!而格式標準也只能參考這個 issue。不過 key、value 的內容和命令列參數一致,應該不難依樣畫葫蘆。

VS Code 整合時可能的 bug

這裡必須提及一個不算罕見的 bug,我自己和同事都遇過:

yapf 對你放置在專案根目錄的 Python 檔案,無法格式化。但對其餘子目錄的檔案卻能夠正常運作

此時如果你直接嘗試用yapf指令格式化該檔案,應該會出現下列錯誤訊息:

1
yapf: toml package is needed for using pyproject.toml as a configuration file

如錯誤訊息所示,這很可能是你的專案目錄裡面已經存在 pyproject.toml(無論裡面是否有關於 yapf 的設定),但專案的虛擬環境中,沒有安裝toml這個套件。所以我們必須安裝它

isort

自動排序 import!超級重要。所幸 isort 的文件非常詳盡,以下案例皆取自文件。

直接設定 isort

1
2
3
[tool.isort]
profile = "hug"
src_paths = ["isort", "test"]

如果你的格式化器是選用 Black,那建議一定要有這個相容 Black 設定

1
2
[tool.isort]
profile = "black"

參考 Black 文件profile = "black"對 isort 的主要影響如下(ChatGPT 整理):

  1. 多行輸出 (multi_line_output): 被設為 3,意味著每個 import 會放在單獨的一行
  2. 包括結尾逗號 (include_trailing_comma): 這會被設為 True,這符合 Black 的行為。
  3. 強制網格包裝 (force_grid_wrap): 被設為 0,這表示不會強制所有 import 都必須放在同一行
  4. 使用括號 (use_parentheses): 被設為 True,這意味著多行 import 會使用括號
  5. 確保註釋前有新行 (ensure_newline_before_comments): 這會被設為 True。
  6. 行長 (line_length): 這會被設為 88,這是 Black 的預設行長。

換句話說,如果你同時使用了 Black 和 isort,卻沒有設定profile = "black"兩者很可能會產生衝突,導致格式化結果不一致。

注意!profile = "black"只是針對 Black 的「預設」行為,如果你有自訂 Black 的設定,比如變更單行字元上限為 100,那在 isort 中也要再次設定

1
2
3
4
5
6
[tool.black]
line-length = 100

[tool.isort]
profile = "black"
line_length = 100

結語:我的 pyproject.toml 使用習慣

將大部分工具的設定,集中至 pyproject.toml,這個想法很不錯,有點為電腦「整線」的味道。

不過進一步說,個人認為 pyproject.toml 更適合那些「只需要簡單幾項設定」的套件,比如本文的 yapf、isort 等。畢竟寥寥數行的設定就單獨一個檔案,確實很佔空間。

而像pytest.inipylintrc這種,有著較多項目的設定檔,或許還是單獨一個檔案會比較妥當。

總之,有了pyproject.toml,專案要存放工具套件設定,將迎來更大的彈性,擁有更多選擇的自由

即使不考慮這些,衝著 Poetry 和 Black 都強制採用了 pyproject.toml——你,也值得擁有!😎