Python 開發:Ruff Linter、Formatter 介紹 + 設定教學
文章目錄
from Pixabay
2024/05/25
:新增系列第二篇〈Python 專案從 Flake8、Black 遷移至 Ruff 指南〉;刪除「緣起」、「工作上我還未採用 Ruff 的理由」段落。
2024/05/10
:重新編輯全文,新增、刪除部分內容,補充更多設定實例。調整大量段落表述方式,提升文章流暢度。可視為 v1.5 版。
Ruff 從去年(2022)6 月正式開源起算,已過去一年半,知道此工具的開發者也愈來愈多。雖然在去年就聽過它,但我卻一直沒有任何行動。
隨著前陣子 v0.1.0 的發布(先別覺得這版本號怎麼乍看像早期測試版本🤣,前一版可是 v0.0.292),我感覺時機已到,所以進行了一番研究、嘗試,並寫下了本文。
本文目錄
- 本文主旨與目標讀者
- Why Ruff?
- 本文範例程式碼
- Ruff 介紹篇
- Linter 部分:取代 Flake8 與 isort 等等
- Formatter 部分:取代 Black
- 單、雙引號議題
- Ruff 設定篇
- 一、pyproject.toml 設定
- 二、VS Code 套件設定
- 三、pre-commit 設定
- Ruff 設定篇小結
- 結語:Time to Ruff
系列:Python Ruff 教學
本文主旨與目標讀者
我在〈Python 開發:pre-commit 設定 Git Hooks 教學〉曾提過:
考慮到「pre-commit」這個主題已經有數篇文章珠玉在前,我下筆前都已拜讀完。本文的論述重心會盡可能與這些文章錯開,或乾脆直接引用,以降低不必要的重複感。
而 Ruff 也是如此,推薦你先讀過這篇〈新世代的 Python Linter - Ruff〉,然後再繼續閱讀本文,以獲得更全面的理解。
接下來對 Ruff 的介紹,將有不同的側重與著墨,以及更多設定細節。
本文主旨
本文主要分為三大部分:
- 前言。
- Ruff 介紹篇。
- Ruff 設定篇。
一開始不免俗地先講述我研究與使用 Ruff 的動機與理由,接著介紹 Ruff 的主要功能——linter 與 formatter。包括它能取代的工具、特色、優勢。
而「設定篇」著重的,則是在初步了解 Ruff 之後,如果真要採用它,我們還需要處理好哪些環節,才能在開發中流暢地使用它。
目標讀者
本文的目標讀者有三:
- Python 開發中還沒有認真用過 linter、formatter 作為規範工具,想要開始導入,並希望用 Ruff 一次解決的人。
- 長期使用「Flake8 + isort + Black」這套 Python 開發經典組合,對 Ruff 有高度興趣,想要試試看的人。
- Code Formatting in Python 愛好者。
簡言之,這篇文章是寫給「已經用 Python 一段時間」的人,而非 Python 初學者。
不過話說回來,如果初學者一開始就肯使用 Ruff 好好規範自己所寫的 Python code,不正是一個絕佳的開端嗎?😎
Why Ruff?
什麼是 Ruff,一句話說就是:
An extremely fast Python linter and code formatter, written in Rust.
而我認為採用 Ruff 主要基於下面兩大理由。
一、快,就是快
下圖來自 Ruff 的 GitHub 首頁,很好表達了為何要採用 Ruff——因為快。
用 Rust 寫的,快,自然不在話下。
而「極致的快」在很多時候會大幅改變你做事的方式——不過這對於大型專案可能才更有感。
對於中、小型專案,我覺得以下第二個理由更加重要。
二、All-in-One:整合眾多套件
不止是速度,Ruff 的「整合能力」也不容小覷。
按照官方文件,有了 Ruff 後,以下的工具都不必再安裝了:
想想是不是有一點「pyproject.toml」企圖統一天下,整合眾多工具設定檔的味道呢?
相關文章:Python 開發設定檔——pyproject.toml 的崛起(待發表)
上述這些工具,尤其是最常用的 Flake8、isort、Black Formatter,如果現在只需要用一個工具就能實現,而且還更快、更好。
那我們何樂而不為?
本文範例程式碼
本文中所提到的 Ruff 具體設定內容,包括 pyproject、VS Code、pre-commit 部分,都會加到這個 GitHub 專案中。
其中值得參考的檔案有:
前言結束,接下來我們進入正題。首先是對 Ruff 的介紹。
Ruff 介紹篇
Ruff 是一個 Python linter + formatter,雖然大部分人可能只使用它的 linter 部分,因為 formatter 還處於 beta 階段。
Ruff formatter 是一個相對獨立的功能,在開源後才開始建構。你完全可以只用 Ruff 的 linter,而依舊使用 Black 或 yapf 來格式化程式碼。
不過,即使只看 Ruff 的 linter 部分,它也並不是一個「單純的 linter」而已。因為它的實際行為超過了靜態分析(static analysis)。
所以,想要全面了解 Ruff,我們需要具體知曉,它能夠做到哪些事、取代哪些工具。
Linter 部分:取代 Flake8 與 isort 等等
Ruff 的核心部分就是它的 linter,這點無庸置疑。
而 Flake8 作為 Python 開發中最流行的 linter,自然是 Ruff 首要取代的目標。所以 Ruff 連錯誤代碼都盡可能與 Flake8 一致,也是考慮到遷移的成本。
Autofix 功能
不僅如此,Ruff 還能取代一眾「帶有 formatter 功能的 linter」——isort 就是其中的代表。而常見的 pyupgrade 也是。
從這點可知,即使你沒有使用 Ruff 的 formatter 功能,它的 linter 部分還是帶有一定的 format 能力——也就是 autofix。
這個特性真的很方便,但同時帶來了一定的複雜。
方便的是,你可以只安裝 Ruff linter,就獲得多種 linter 附帶的 format 行為。而複雜則在於:設定上的細節也比一般 linter 更多。
小結:Ruff linter 是個簡易版 formatter
我原本以為 Ruff(不考慮ruff-format
部分)只是一個比較快的靜態分析工具,顯然事實並非如此。
總之,我們只要記得:Ruff linter 有著簡易 format(autofix)能力——它是個簡易版的 formatter。
這和 Flake8 只做單純的靜態分析不同,Ruff linter 在檢查過程中,能夠直接對程式碼進行修改。當然,如果你不喜歡,這功能是可以關閉的。
Formatter 部分:取代 Black
我相信從 Ruff 的開源之初,想過要做 formatter 功能。畢竟 Rust 這麼快,只做 linter 未免太可惜了!而且兩者在使用上往往密不可分。
和 linter 不同,formatter 具有強烈的排它性。不同的 formatter 之間,沒有相容可言。不像多個 linter 還可以疊加使用——如果你不嫌一堆警告很煩XD。
如果要寫一個全新的 formatter,就必須有足夠的理由,讓開發者願意放棄當前方案,採用你的新工具——這很不容易。
比較可行的做法,是相容並取代市場上現有的 formatter。
既然要選一個,那當然是選 Black——目前最流行的 Python formatter。
Black Formatter 相關文章:
和 Black 的相容性
因此,和對待 Flake8 一樣,Ruff formatter 必須與 Black 格式化後的結果,有高度的相容(一致)。否則你換了它的 formatter,卻帶來一堆格式化結果改動,絕對會造成遷移負擔與採用意願的下降。
按照官方文件中「Black compatibility 」這段可知:
Specifically, the formatter is intended to emit near-identical output when run over Black-formatted code. When run over extensive Black-formatted projects like Django and Zulip, > 99.9% of lines are formatted identically. When migrating an existing project from Black to Ruff, you should expect to see a few differences on the margins, but the vast majority of your code should be unchanged.
兩者的相容性(一致性)高達 99.9% 以上。意味著你可以放心地從 Black 換到 Ruff,而不用擔心格式化結果的差異。
(但是有一個「但書」,請見下面的「單、雙引號議題」)
友善提醒:formatter 遷移的變動,最好獨立一個分支或 commit,一次就把所有格式化差異都處理掉。而不要和其他功能性的變動混在一起。
單、雙引號議題
Python 允許開發者自由選擇要在程式碼中使用單引號或雙引號。
只有在比如 docstring 這種連續使用 3 個引號的場景,慣例上要求使用 3 個雙引號。
如果你對 Black 有一些了解,應該會知道,早期 Black 是完全不管你習慣用單引號還是雙引號,它一律把你的 Python 程式碼格式化為雙引號!
這小小的硬性規則帶來了巨大的反彈,畢竟 Python 開發者中想必有不少人和我一樣,是「單引號」的支持者。
最後,Black 開發團隊也不得不妥協(這是 Black 少數的妥協,因為該工具本身就是以「不妥協」為賣點🤣),加入了skip-string-normalization
選項。
Ruff Formatter 的不同處理方式
而 Ruff linter 作為 Black 的替代方案,也會遇到相同的「困境」。不同於 Black,目前 Ruff linter 提供的是quote-style
這個選項:
1 | quote-style = "single" # 選項有 "single" 和 "double" |
如果設為"single"
,Ruff 會把專案中的所有雙引號,都替換為單引號。不過,在上述慣例部分,Ruff 還是保留雙引號,不用擔心。
需要提醒的是,如果你的專案目前是單雙引號混用,因為 Ruff 會強制統一引號風格,這勢必會帶來不少的格式化差異。如上所述,這些變動最好統一獨立處理。
接下來,我們要進入 Ruff 的「設定篇」。
Ruff 設定篇
當要採用一個新的 linter、formatter 時(尤其站在團隊開發考量),以下這三個部分的支援成熟度,是我一定會慎重考慮的。
設定檔(pyproject.toml 支援)
複雜的工具都會有自己的設定檔,讓你可以客製化需求。如果能支援 pyproject.toml 則會更受到我的青睞。
VS Code 套件支援
它能讓你在寫程式的同時,就能夠看到 linter 發出的警告,而不必等到 commit 之際才被 pre-commit 擋下來。
相較於 linter,formatter 更需要有自己的 VS Code 套件。讓你能直接在 VS Code 中進行格式化,而不用透過 CLI 指令或等到 pre-commit 時才自動修正。
即使我們只用 Ruff 的 linter 功能,但因為有「autofix」存在,如前所述,它本質上也是一個簡易的格式化器,所以最好有 VS Code 整合。
pre-commit 支援
pre-commit 是團隊協作中一道重要的關卡,我在「為什麼要使用 pre-commit?」中已有相當的闡述。
對於一個開發工具,我們主要關心的是:「它是否提供 pre-commit hook?」
以上 3 點,Ruff 都有完整支援。下面就來一一解說。
一、pyproject.toml 設定
Ruff 總共支援三種設定檔:pyproject.toml
、ruff.toml
、.ruff.toml
。
如果不知道怎麼開始,參考文件是最快上手的方式。以下的範例與解說,我採用設定檔格式是pyproject.toml
。
Ruff 因為功能眾多,設定上較為複雜。所幸,大部分時候,我們只需要設定一些基本的項目。
基本設定與解說
首先,因為在pyproject.toml
中,所以設定的 key 定是[tool.xxx]
格式。
Linter 部分,一些基本的設定比如line-length
和 Flake8 類似。其中select
和沒有列出的ignore
相對重要。
Ruff 預設只會顯示 E
和 F
系列的錯誤訊息(而 Flake8 還有 W
系列)。想要增加或排除特定部分的錯誤訊息警示,就得透過select
欄位調整。比如我的設定:
1 | [tool.ruff] |
select
加上了I
和UP
。分別代表了 isort 和 pyupgrade。一旦你開啟了它們,Ruff 就會提示相關錯誤,並在有錯誤時自動修正。(autofix 預設為開啟)
因為開啟了UP
,所以我必須設定target-version
(這裡為py310
),意味著 Ruff 會將程式碼中舊的寫法自動轉換(autofix)為 Python 3.10 的寫法。
總之,可設定的項目非常豐富。
設定懶人包
如果你剛開始用 Ruff,不知道該設定哪些項目。我建議你可以先設定以下幾個:
1 | [tool.ruff] |
二、VS Code 套件設定
Ruff 的 VS Code 套件在 2022 年 12 月首次發表。
套件的重要性前面有提過,在此再次闡述:
- Linter:在 IDE 中直接提示,不必等到後續流程才發現錯誤。
- Formatter:在 IDE 直接格式化(尤其配合「存檔時格式化」),不需透過指令。
基於上述兩個需求,如果一個全新的 linter 或 formatter 沒有相關的 VS Code 套件,我不會考慮使用。
安裝與設定 Ruff 套件
套件安裝後就可以直接使用,如果你的專案中已有專屬的 Ruff 設定檔,我覺得不需要再特別設定 VS Code Ruff 套件部分。
不過還是提供我的設定:(以下為 VS Code settings.json
內容)
1 | "ruff.enable": true, |
特別提醒,如果你在專案中有已經有自己的 Ruff 設定檔,那就不要設定 VS Code Ruff 套件的「args
」部分。比如:
1 | "ruff.lint.args": ["--fix"], |
因為 VS Code 會優先使用settings.json
的設定。
建議設定 VS Code Python
因此,Ruff 套件我覺得不特別設定也沒關係,有專案中的 Ruff 設定檔就夠了。
相對的,VS Code 的 Python 部分則建議要設定。開發會更順手。
具體內容,Ruff 套件首頁有範例。我們直接看最完整的版本:
1 | "[python]": { |
效果說明:
editor.formatOnSave
:存檔時自動格式化(對所有 formatter 都有效)。source.fixAll
:存檔時自動 fix。(類似 pre-commit 時,hook 的自動修正)source.organizeImports
:存檔時自動排序 imports。
我想上述這些 Python 設定才是 Ruff 在 VS Code 中流暢使用的重點。
不同專案間切換
不一定每個專案都用 Ruff。在不使用 Ruff 的專案中,Ruff linter 還一直提示你錯誤,這樣會很煩。
此時可以讓 Ruff 在這個「工作區」停用!做法如下圖。
Formatter 部分,因為每個專案只能選定一種格式化器,選好就沒有衝突問題。
三、pre-commit 設定
pre-commit 設定相對單純,更細部的行為,hook 會自動讀取設定檔中的內容。
1 | - repo: https://github.com/astral-sh/ruff-pre-commit |
這裡只要注意版本和 hook 的 id 即可。
Ruff 設定篇小結
Ruff 的設定真的滿多樣且可以很複雜。但是,如果你只是想要快速上手,其實只需要設定一些基本的項目。
如果你不清楚究竟有哪些項目可以調整,又想了解更多。
除了研究官方文件、Github 頁面的 README 外,去看看那些已經採用了 Ruff 的開源專案設定檔,也是很好的學習!
比如,我就習慣參考 FastAPI 的設定:
2024/05/25
:引用內容為舊版 0.1.x 設定。新版設定可以直接參考上述連結。
1 | [tool.ruff] |
而且 FastAPI 真的很貼心,還加了註解!
結語:Time to Ruff
Ruff 對我的重要性,雖然不如 Poetry 套件管理器。但這樣的工具依舊讓人興奮且充滿期待。
我並不認為目前的 Python 開發一定要用上 Ruff,但值得你嘗試一下。
如果你也是一個 Code Formatting 愛好者,那麼 Ruff 絕對是一個值得你花時間研究的工具。
更重要的是,由 Rust 所引發的 Python 生態革命,現在才剛剛開始。
相關文章