Python 專案從 Flake8、Black 遷移至 Ruff 指南
文章目錄
from Pixabay
上個月,我將工作上幾個專案的 Python linter、formatter,從原本的 Flake8、isort、Black Formatter 遷移至 Ruff。
本來以為很簡單,應該半小時就可以搞定。不料細節比想像中的多,前前後後還是花了近 2 小時。
可能我比較龜毛吧!
正因耗費的時間有點多,所以寫下這篇懶人包教學,作為讀者遷移時的參考。
本文主旨與目標讀者
這篇文章是寫給,目前正使用上述 Flake8、isort、Black Formatter 作為格式化工具,並打算遷移至 Ruff 的 Python 開發者。
受眾就是〈VS Code 設定 Python Linter、Formatter 教學〉中描述的那樣。在專案中使用上述工具,也安裝了相關的 VS Code 套件,設定好 pre-commit hook,以及使用 pyproject.toml 作為這些工具的設定檔。
而本文的目標,就是讓專案的 linter、formatter,最終都統一使用 Ruff。
系列:Python Ruff 教學
為什麼要用 Ruff?
在系列第一篇,即〈Python 開發:Ruff Linter、Formatter 介紹 + 設定教學〉中,我闡述了使用 Ruff 的兩大理由。
在工作上用了 Ruff 近 2 個月後,我覺得這兩個理由都很真實。而且,速度帶來的差異與感受,比我想像的更多!
除此之外,還有一個我之前沒想到的優點——「避免衝突」。
工具間的行為衝突
我們知道,linter 與 formatter 如果進行客製化設定,那兩者對於格式的要求可能會發生不一致,這在設定上需要留意。
比如最常見的「單行最大字元上限」。如果在 Flake8 從 79 字元改成 100,那 isort 和 Black 也要跟著設定才行!
尤其 isort 和 Black 都是格式化器,兩者都有「格式化程式碼」的能力,如果設定不一致,會帶來一定的困擾。
所以 isort 才會有以下這個常見設定:
1 | [tool.isort] |
為的就是和 Black「達成一致」。
而這些潛在的「衝突」議題,在統一使用 Ruff 後,將不復存在。
遷移任務清單
我們先看一下,遷移至 Ruff 需要完成的任務有哪些:
- 更新 pyproject.toml:新增、移除套件。更新工具設定檔內容。
- 更新 VS Code 套件、設定格式化行為。
- 更新 pre-commit 設定。
- 統一格式化專案所有程式碼。
以下說明其中的重點。
一、更新 pyproject.toml
這是所有步驟中,比較需要費心的部分。
首先是移除舊套件,並新增 Ruff。
在此假設你也是用 Poetry 來管理虛擬環境,所以我們可以直接修改 pyproject.toml 的 Poetry 設定部分:
1 | [tool.poetry.group.dev.dependencies] |
修改完成後,記得執行poetry lock
和poetry install
。
一般使用 Poetry,套件版本的指定我們會採^
運算子,讓套件有一個自動升級範圍,增加相容性。
但 linter、formatter 這類工具,因為還要配合 pre-commit hook,所以我幾乎都是固定版本、手動更新——尤其 Ruff 更新版本就要重新建立 pre-commit hook 的虛擬環境,過程相對耗時。
Ruff Linter、Formatter 設定
接下來是重頭戲——在 pyproject.toml 中設定 Ruff 行為。我們先看結果:
1 | [project] |
為了教學說明與維護方便,我加上了大量註解。
我強烈建議你在工作專案中,也要為這些設定適度加入註解——畢竟不是每個人都熟悉 Ruff。而且,Ruff 可設定的項目眾多,久了自己可能也會忘記。
大部分的設定,我們在第一篇都已提過,不再重複。
以下補充一些值得留意的點。
一、必須明確區分 Linter 和 Formatter 設定
從 Ruff 0.2 版開始,對於 section 名稱(即tool.ruff
、tool.ruff.format
等)有比較嚴格的要求。可以參考這個 PR。
簡言之,共用設定放tool.ruff
。如果是 linter 專用的設定就要放tool.ruff.lint
,Ruff Formatter 的設定則要放tool.ruff.format
。
不像以前那麼寬鬆。
設定不合法時,會有錯誤提醒,留意一下即可。
二、requires-python 設定
這個 key 是讓你指定專案的 Python 版本,有兩種表達方式。第一篇我們用的是這種,本文用的是第二種。
官方文件推薦使用第二種:
If you’re already using a
pyproject.toml
file, we recommendproject.requires-python
instead, as it’s based on Python packaging standards, and will be respected by other tools.
我們從善如流。
三、quote-style
最後不忘提醒,Ruff formatter 的 quote-style 設定,將會使整個專案的「引號」風格趨於統一(PEP 8 慣例除外,比如 docstring)。
換句話說,有些人習慣用單引號,有些則是雙引號,遷移後將會統一。
這對任何專案都是不小的變動,遷移前需要認真考慮,尤其是團隊對於要用哪一種風格,是否已達成初步的一致。
比如我自己是單引號支持者,但是在工作中,為了提高大家採用新工具的意願,我願意稍作妥協。所以上面的例子是雙引號。
二、更新 VS Code 套件
這部分就很簡單了。
移除舊套件,安裝新套件。但如果你還有其他專案需要使用 Flake8 等工具,那也不能輕易移除。此時可以參考前一篇的「在工作區停用」。
以免不同套件相互干擾。
至於 Python 部分的 VS Code 設定,可直接參考前篇。
三、更新 pre-commit 設定
這部分比以前簡潔很多:
1 | repos: |
畢竟以前要設定三個工具,如今只剩下一個。
四、統一格式化
統一格式化,而不是一邊開發一邊格式化,以免造成功能更新與格式化更新混淆,導致 code review 時的大量視覺干擾。
怎麼做呢?回到命令列以指令操作。
在專案根目錄使用下列指令:
1 | ruff format |
就這麼簡單,0.1.7 版以後,它實際上有一個預設參數是「.
」,也就是整個目錄。
詳情可參考官方文件。
注意,有些檔案沒必要格式化,比如 Django 專案 migrations 目錄下的資料庫遷移檔,可以在 pyproject.toml 中設定exclude
。
補充:pyupgrade 真是棒!
Ruff 整合了 pyupgrade,讓我第一次認識到這個強大的工具。
我強烈建議,一定要開啟 pyupgrade 功能!讓專案中的 Python 語法維持更高程度的一致性。
畢竟,在團隊協作中,「一致性」有時比慣例更加重要。
例示:修正 type hints 語法
比如 Python type hints 中,關於 Optional 的寫法,3.9 以前會這樣寫:
1 | from typing import Optional |
Optional 本身是 Union 在特定情況下的別名,但它常常讓人感到困惑。
而在 Python 3.10 之後,上述寫法可以改為:
1 | def foo(arg: int | None): |
後者顯然更加簡潔且直觀。(讓你一望即知,參數型別不是 int 就是 None)
不過話說回來,這個例子並不是一個好的 type hints 示範,理由可參考這篇〈Python Type Hints 教學:我犯過的 3 個菜鳥錯誤〉底下,良葛格的留言。
pyupgrade 功能與小結
具體來說,pyupgrade 會自動將舊的 type hints 語法轉換為新版語法。
上述的Optional
,將自動修正為|
寫法——只要我指定了 Python 3.10 以上的版本。
但 pyupgrade 會修正的部分,遠不止 type hints,這只是一個例子。
啟用 pyupgrade,可以自動幫你把這些舊語法轉換為新版(由 requires-python 指定目標 Python 版本)語法。
這不僅讓你的程式碼更乾淨,也能避免不同成員寫出風格不一致的 Python 程式碼。
相關文章