新世代的 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 外掛也都已經實作完成,但還有少數 EW 還未實作完成,不過影響並不大,詳見 FAQ - Ruff

如果你已經有一些 Flake8 的設定檔,可以使用 flake8-to-ruff · PyPI 套件幫忙轉成 Ruff 的設定檔。

Ruff 的設定檔可以用這 3 種檔案名稱進行設定,如果你有使用 Poetry 就可以與 pyproject.toml 進行整合,我個人偏好另存 1 個檔案:

  1. pyproject.toml
  2. ruff.toml
  3. .ruff.toml

使用 pyproject.toml 的話,需要加上 [tool.ruff] 區塊才能讓 Ruff 正確讀取到設定,例如:

[tool.ruff]
select = ["E", "F"]
ignore = []

如果是使用 ruff.toml.ruff.toml 就不用額外加上 [tool.ruff] 區塊,例如:

select = ["E", "F"]
ignore = []

懶人的話可以先只設定只顯示 EF 開頭的錯誤,省略 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.

Ruff

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!