新世代的 Python Linter - Ruff
Posted on Mar 23, 2023 in Python 程式設計 - 中階 by Amo Chen ‐ 4 min read
自從 Rust Programming Language 開始展露頭角之後,很多工具都開始見到 Rust 的影子,或是底層由 Rust 重寫,或是關鍵部分由 Rust 實作,這些由 Rust 所改寫的工具幾乎都有個共通點 —— 快!
Ruff 就是 1 套底層由 Rust 實作的 Python Linter, 號稱速度快上其他常見的 Linter 約 10 ~ 100 倍之間,同時支援快取(cache)、自動修正(autofix)、Python 3.11, 甚至還提供 pre-commit 以及 VS Code 擴充(extension)等功能,除了用心還有貼心!
另外其功能近乎 Flake8 的完整功能(詳見 FAQ - Ruff ),因此許多人也放心選擇使用 Ruff 作為其開發時的 Linter 使用,包含 FastAPI, pandas, Apache Airflow 等開發團隊都已經使用 Ruff 作為其 Linter!
一起來認識 Ruff 這個新世代的 Python Linter 吧!
本文環境
- macOS
- Python 3.9
什麼是 Linter?
Linter 是一種程式碼檢查工具,可以檢查程式碼中的語法錯誤、 coding style 問題和潛在的漏洞,並提供建議和修正方法。通過使用 Linter 工具,開發者可以快速地發現並修正程式碼中的錯誤,提高程式碼的品質。
Python 常見的 Linter 工具包括 Flake8, Pylint 等等, Linter 已經成為現代軟體開發中必不可少的一部分,如果你還沒使用過 Linter, 建議從今天起就開始學習如何使用。
安裝 Ruff
雖然 Ruff 是用 Rust 實作,不過其安裝也相當簡單,不需要特別安裝 Rust 相關執行環境,只要透過 pip 指令安裝即可:
$ pip install ruff
如果你不想安裝,只想體驗 Ruff, 也可以到 Ruff Playground 網頁版,體故意錯 code 體驗看看 Ruff, 例如以下畫面是 Ruff 抓到變數 x
未被使用的畫面:
Ruff 使用指令
Ruff 的使用指令相當簡單,執行 ruff check <檔案/資料夾>
就可以進行檢查,例如:
$ ruff check test.py
也可接受萬用字元 *
一次檢查所有 .py
檔案。
$ ruff check path/to/*.py
Ruff 與 Flake8 速度比較
既然 Ruff 宣稱超快,那當然要實際比較 Ruff 與 Flake8 的執行速度試試看,以下是我們的受測程式 test.py, 裡面的 sys 是沒被用到的模組,我們拿它做 Ruff 與 Flake8 速度比較:
import sys
import unittest
class MyClass(object):
do_test = True
def skipUnlessHasattr(obj, attr):
if hasattr(obj, attr):
return lambda func: func
return unittest.skip("{!r} doesn't have {!r}".format(obj, attr))
@skipUnlessHasattr(MyClass, "test")
class MySkippedClass(unittest.TestCase):
def test_nothing(self):
pass
if __name__ == '__main__':
unittest.main()
先用 Flake8 檢查一遍,可以看到花費時間約 0.21 秒:
$ /usr/bin/time -a flake8 test.py
test.py:1:1: F401 'sys' imported but unused
0.21 real 0.06 user 0.03 sys
以下是 Ruff 檢查的執行時間,可以從結果看到花費時間約 0.1s, 快了足足 1 倍有餘:
$ /usr/bin/time -a ruff check test.py
test.py:1:8: F401 [*] `sys` imported but unused
Found 1 error.
[*] 1 potentially fixable with the --fix option.
0.10 real 0.00 user 0.01 sys
自動修復錯誤(autofix)
Linter 掃描到問題之後,最好是能夠自動修復錯誤,不僅省時也相當省力, Ruff 也支援 autofix 的功能,只要加上 --fix
參數就能夠啟用自動修復,例如前述 F401 [*]
sys imported but unused
的問題可以如此修正:
$ ruff check --fix test.py
執行結果如下,可以看到 Ruff 幫我們修正了 1 個錯誤:
$ ruff check --fix test.py
Found 1 error (1 fixed, 0 remaining).
只要打開 test.py 就可以發現 sys
已經被刪除了。
Linter 規則設定
眼尖的人應該會發現 Ruff 的錯誤碼與 Flake8 相同,這是由於 Ruff 設計時就考慮到要能夠取代 Flake8 的緣故,所以錯誤碼都遵從 Flake8 的錯誤代碼,其中 F 開頭的錯誤代碼都已經實作以及許多常見的 Flake8 外掛也都已經實作完成,但還有少數 E 與 W 還未實作完成,不過影響並不大,詳見 FAQ - Ruff 。
如果你已經有一些 Flake8 的設定檔,可以使用 flake8-to-ruff · PyPI 套件幫忙轉成 Ruff 的設定檔。
Ruff 的設定檔可以用這 3 種檔案名稱進行設定,如果你有使用 Poetry 就可以與 pyproject.toml
進行整合,我個人偏好另存 1 個檔案:
pyproject.toml
ruff.toml
.ruff.toml
使用 pyproject.toml
的話,需要加上 [tool.ruff]
區塊才能讓 Ruff 正確讀取到設定,例如:
[tool.ruff]
select = ["E", "F"]
ignore = []
如果是使用 ruff.toml
與 .ruff.toml
就不用額外加上 [tool.ruff]
區塊,例如:
select = ["E", "F"]
ignore = []
懶人的話可以先只設定只顯示 E 與 F 開頭的錯誤,省略 E501 字數超過每行上限的錯誤,再視自己的需求慢慢微調:
select = ["E", "F"]
# Never enforce `E501` (line length violations).
ignore = ["E501"]
更多設定方法可以詳閱 Configuration - Ruff 一文。
錯誤碼解釋與範例
如果想知道某個錯誤碼的意思,可以輸入指令 ruff rule <錯誤碼>
, 它會顯示錯誤的說明與如何修正的範例,例如 E401:
$ ruff rule E401
# multiple-imports-on-one-line (E401)
Derived from the **pycodestyle** linter.
## What it does
Check for multiple imports on one line.
## Why is this bad?
Per PEP 8, "imports should usually be on separate lines."
...(略)...
VS Code 擴充
如果你習慣使用 VS Code 進行開發, Ruff 也提供 Ruff - Visual Studio 擴充 供開發者安裝,如此就能夠在 VS Code 中直接使用 Quick Fix, Fix all 等功能。
Pre-commit
除了 VS Code 擴充,Ruff 也有 pre-commit 外掛(plugin)可以使用,只要在 .pre-commit-config.yaml
加上以下設定即可:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.257'
hooks:
- id: ruff
如此一來就能夠在執行 git commit
指令時自動以 ruff 指令進行檢查。
p.s. 關於如何設定 pre-commit 可以參考此文章 用 pre-commit 輕鬆提升程式碼品質 。
其他指令
其他指令可以使用 ruff help
指令列出:
$ ruff help
Ruff: An extremely fast Python linter.
Usage: ruff [OPTIONS] <COMMAND>
Commands:
check Run Ruff on the given files or directories (default)
rule Explain a rule
config List or describe the available configuration options
linter List all supported upstream linters
clean Clear any caches in the current directory and any subdirectories
help Print this message or the help of the given subcommand(s)
其中 ruff clean
是用來清理 linter 快取的指令,如果你的 CI 流程需要確保每次都在沒有 cache 的情況下執行,就需要多設定一道 ruff clean
清理快取。
總結
Ruff 是一款快速且容易使用的 Python Linter, 使用過程可以感覺到開發團隊的用心,如果您正在尋找一個可靠的 Python Linter 工具,或者考慮從 Flake8 搬家到其他 Linter 的話, Ruff 是一個值得考慮的選項。
References
GitHub - charliermarsh/ruff: An extremely fast Python linter, written in Rust.