Python 開發:Ruff Linter、Formatter 介紹 + 設定教學
文章目錄
from Pixabay
Ruff 從去年(2022)6 月正式開源起算,已過去一年半,知道此工具的開發者也愈來愈多。雖然在去年就聽過它,但我卻一直沒有任何行動。
隨著前陣子 v0.1.0 的發布(先別覺得這版本號怎麼乍看像早期測試版本🤣,畢竟前一版可是 v0.0.292),我覺得時機已到,所以進行了一番研究、嘗試,於是有了本文的誕生。
本文目錄
- 緣起
- 本文主旨與目標讀者
- Why Ruff?
- 本文範例程式碼
- Ruff 介紹篇
- Linter 部分:取代 Flake8 與 isort 等等
- Formatter 部分:取代 Black
- 單、雙引號議題
- Ruff 設定篇
- pyproject.toml 設定
- Ruff VS Code 套件設定
- pre-commit 設定
- Ruff 設定篇小結
- 工作上我還未採用 Ruff 的理由
- 結語:Time to Ruff
緣起
工作上,我們團隊使用的 linter、formatter 分別是常見的 Flake8、isort、yapf。這在以往的文章有多次提及。
隨著 Ruff 的聲量與能見度日漸提高,作為一個 Code Formatting 愛好者,我自然也是躍躍欲試——最好是能夠直接應用到工作開發上!
因為團隊還不大(含我共 3 個後端開發者),所以改用 Ruff 是完全有可能的。
不過經過一番研究與考慮,我還是暫時推遲了工作上對 Ruff 的採用(只是把 formatter 從 yapf 換成 Black),原因會在文末說明。
但是!我還是很推薦,從現在就開始使用 Ruff 作為你「個人開發」的預設 linter 甚至 formatter。而 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」企圖統一天下,整合眾多工具設定檔的味道呢?
上述這些工具,尤其是最常用的 Flake8、isort、Black,如果現在只需要用一個工具就能實現,而且還更快、更好。
那我們何樂而不為?
本文範例程式碼
本文中所提到的 Ruff 設定具體內容,包括 pyproject、VS Code、pre-commit 部分,都會加到 Django-Tutorial 這個專案中。
它是我「Django Tutorial」系列文章的範例程式碼,恰好也適合作為其它教學文章的實際素材展示。
git clone
本專案後,可以直接git checkout
到02-ruff
分支。這個分支所在的 commit,就是本文的具體設定內容與改動。
你也可以直接在 GitHub 上查看 commit 內容。
前言結束,接下來我們進入正題。首先是對 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 更多。
簡易版 formatter
我原本以為 Ruff(不考慮ruff-format
部分)只是一個比較快的靜態分析工具,顯然事實並非如此。
總之,我們只要記得:Ruff linter 有著簡易 format(autofix)能力——它是個簡易版的 formatter。
這和 Flake8 只做單純的靜態分析不同,Ruff linter 在檢查過程中,能夠直接對程式碼進行修改。當然,如果你不喜歡,這功能是可以關閉的。
Formatter 部分:取代 Black
我相信從 Ruff 的開源之初,就已經想過要成為一個 All-in-One 工具。畢竟 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% 以上,基本可以放心遷移。
附帶一提,如果你是從 yapf 遷移到 Black,就能夠明顯感受到格式化風格差異所帶來的困擾——程式碼變動的地方太多了!
這種情況最好還是獨立一個分支或 commit,一次就把所有格式化差異都處理掉。而不要直接繼續開發,讓開發中的程式碼、檔案隨著開發進度被新 formatter 自動格式化,因為這會很影響 code review——格式化變動和開發變動混雜。
還處於 Beta 階段
The Ruff formatter is available as a production-ready Beta as of Ruff v0.1.2.
引言中的「production-ready Beta」超連結,指向 Ruff Formatter 的官方介紹文章與發展現況總結。
所謂的 beta 並不是還有很多 bug,更像是「設計與方向上」還沒有完全底定——比如下面要討論的「單、雙引號」議題。所以才會在前面加上「production-ready」。
如果你想要在個人的生產環境中使用,我相信是沒什麼問題。畢竟 FastAPI 專案已經直接使用了!
不過如果是公司專案,我還是會選擇觀望,不急於一時。
單、雙引號議題
Python 允許開發者自由選擇要在程式碼中使用單引號或雙引號。
只有在比如 docstring 這種連續使用 3 個引號的場景——即""" <內容> """
——時,慣例上要求使用雙引號。見 PEP 257:
For consistency, always use
"""triple double quotes"""
around docstrings.
如果你對 Black 有一些了解,應該會知道,早期 Black 是完全不管你習慣用單引號還是雙引號,它一律把你的 Python 程式碼格式化為雙引號!
這小小的硬性規則帶來了巨大的反彈,畢竟 Python 開發者中想必有不少人和我一樣,是「單引號」的支持者。
最後,Black 開發團隊也不得不妥協(這是 Black 少數的妥協,因為該工具本身就是以「不妥協」為賣點、slogan🤣),加入了skip-string-normalization
選項。
Ruff Formatter 發展中
而 Ruff linter 作為 Black 的替代方案,也會遇到相同的「困境」。不同於 Black,目前 Ruff linter 提供的是quote-style
這個選項:
1 | quote-style = "single" |
即使你選了 single,在上述的慣例部分,Ruff 還是會格式化為雙引號,不用擔心。
至於要不要像 Black 加上skip-string-normalization
,目前似乎還沒有定論。
整體而言,這個議題仍然在持續中,有興趣可以關注這個 GitHub Issue 討論串。
這裡只是提醒你,採用 Ruff formatter 會有「單、雙引號」議題(而且和 Black 的處理方式不完全相同)。
至少對我而言,這非常重要。
接下來,我們要進入 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
。反之如果有大量的客製,那獨立一個設定檔可能是比較好的選擇。
如果不知道怎麼開始,參考文件是最快上手的方式。文件中的範例內容同時有著註解式的解說。
不過大部分時候,我們只需要設定一些基本的項目。比如以下是我目前的設定,包含了 linter 與 formatter 部分:
1 | [tool.ruff] |
設定解說
首先,因為在pyproject.toml
中,所以設定的 key 定是[tool.xxx]
格式。
Linter 部分,一些基本的設定比如line-length
和 Flake8 類似。其中select
和沒有列出的ignore
相對重要。
Ruff 預設只會顯示 E
和 F
系列的錯誤訊息(而 Flake8 還有 W
系列)。想要增加或排除特定部分的錯誤訊息警示(包含 autofix),就得透過上述兩個欄位調整。比如:
1 | [tool.ruff.lint] |
在我的設定中,select
加上了I
和UP
。分別代表了 isort 和 pyupgrade。一旦你開啟了它們,Ruff 就會提示相關錯誤,並在有錯誤時自動修正。(autofix 預設為開啟)
因為開啟了UP
,所以我必須設定target-version
(這裡為py310
),意味著 Ruff 會將程式碼中舊的寫法自動轉換(autofix)為 Python 3.10 的寫法。
總之,可設定的項目非常豐富。
Ruff VS Code 套件設定
Ruff 的 VS Code 套件在 2022 年 12 月首次發表。
說真的,如果一個全新的 linter 或 formatter 沒有相關的 VS Code 套件,我絕不會考慮使用。
套件的重要性前面有提過,在此再次闡述:
- Linter:在 IDE 中有直接的提示,不必等到 commit 才發現錯誤。
- Formatter:直接執行格式化(尤其配合「存檔時格式化」),不需透過指令。
絕大部分情況下,linter 提示的錯誤都會直接被 formatter 自動修正,感覺上沒有開啟 linter 提示似乎也無妨?
但是,兩者在少數時候會有不同的行為,所以我認為 linter 的提示仍是必要的。
當然,另一方面也因為我已習慣看 linter 提示👀——沒有會很不自在!
安裝與設定 Ruff 套件
套件安裝後就可以直接使用,如果你的專案中已有專屬的 Ruff 設定檔,我覺得不需要再特別設定 VS Code Ruff 套件部分。
不過還是提供我的設定:
1 | "ruff.lint.args": [ |
建議設定 Python 行為
Ruff 套件我覺得不設定也沒關係,有設定檔就夠了。
除非你不想要為每個專案一一建立 Ruff 設定檔,那就還是得弄一下(會套用到每一個專案)。同時也要考慮不同專案間的設定衝突問題——使用者全局設定 vs 專案設定。
相對的,VS Code 的 Python 部分則建議一定要設定。
這部分的具體內容,Ruff 套件首頁也有完整說明。我們直接看最完整的版本:
1 | "[python]": { |
效果:
editor.formatOnSave
:存檔時自動格式化(對所有 formatter 都有效)。source.fixAll
:存檔時自動 fix。(類似 pre-commit 時,hook 的自動修正)source.organizeImports
:存檔時自動排序 imports。
我想上述這些 Python 設定才是 Ruff 在 VS Code 中流暢使用的重點。
不同專案間切換
還有一個小細節,就是專案之間的切換問題。
因為不一定每個專案都用 Ruff。比如我,只在個人專案使用 Ruff,但工作上還沒有。或是相反的情況。
此時記得要把可能發生衝突的 VS Code 套件(主要是 linter 和 isort)在「工作區」範圍內停用!做法如下圖。不然 linter 部分很可能一起運作,產生意料之外的結果。
Formatter 部分,因為每個專案只能選定一種格式化器,比較沒有衝突問題。
pre-commit 設定
pre-commit 設定相對單純,更細部的行為,hook 會自動讀取設定檔中的內容。
1 | - repo: https://github.com/astral-sh/ruff-pre-commit |
這裡只要注意版本和 hook 的 id 即可。
Ruff 設定篇小結
Ruff 的設定真的滿多樣且可以很複雜——儘管它的預設值已能滿足大多數人。
如果你不清楚究竟有哪些項目可以調整,又想了解更多。除了研究官方文件、Github 首頁的 README 外,去看看那些已經採用了 Ruff 的開源專案的設定檔,也是很好的學習!
比如,我就習慣參考 FastAPI 的 Ruff 設定:
1 | [tool.ruff] |
而且 FastAPI 真的很貼心,還加了註解!
工作上我還未採用 Ruff 的理由
結語之前,講講我在一番研究後,仍未建議團隊在此刻就採用 Ruff 的理由。
放到最後不是為了賣關子,而是看到這裡,你應該比較能夠理解其中的顧慮。
主要有三個。
一、設定相對複雜
首先我覺得 Ruff 的設定太多樣了,畢竟它一口氣整合了這麼多的工具。
而且要同時考慮 pyproject.toml、VS Code、pre-commit 的整合,加上各種 linter 在設定上的開關,想想有點頭痛。
這會帶來一定的認知負擔,同事可能會覺得「搞這些真的有必要嗎?」——其實我也這麼想🤣
二、需求不足
我們目前的專案最多只能算中型,用「Flake8 + isort + Black Formatter」經典組合已能運作良好。改用 Ruff,可能看不出太大差別。
如果看不出差別,設定又要重新調整、學習,難免讓人卻步。
三、讓子彈再飛一會兒
說起來,前兩個理由並不算什麼重大阻礙,只是也沒有明顯的動力。
我打算等 Ruff formatter 的「beta」字樣拿掉後,直接「一換三」一次到位。
所以,不妨讓子彈再飛一會兒。
結語:Time to Ruff
Ruff 現階段對我的重要性,顯然還遠不如 Poetry 套件管理器。但這樣的工具依舊讓人興奮且充滿期待。
我並不認為目前的 Python 開發一定要用上 Ruff,但值得你嘗試一下。
如果你也是一個 Code Formatting 愛好者,那麼 Ruff 絕對是一個值得你花時間研究的工具。
更重要的是,由 Rust 所引發的 Python 生態革命,現在才剛剛開始。
相關文章