快轉到主要內容
  1. 教學文章/

Ruff:用 Rust 寫的 Python Linter,快到你會懷疑人生

·4 分鐘· loading · loading · ·
Python Ruff Linter Formatter Code-Quality
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
Python 學習 - 本文屬於一個選集。
§ 15: 本文

一. 前言
#

拍拍君每次寫完 Python 準備 commit 的時候,都要跑一輪 flake8、isort、black⋯⋯光等它們跑完就夠喝一杯咖啡了 ☕

後來發現了 Ruff 這個工具,第一次跑的時候差點以為它壞掉了——怎麼一瞬間就跑完了?!

Ruff 是用 Rust 寫的 Python linter + formatter,號稱比 flake8 快 10 到 100 倍。而且它不只是快,還把 flake8、isort、pyupgrade、pydocstyle 等一堆工具的規則整合在一起,一個工具搞定所有事。

今天就來帶大家認識這個讓拍拍君回不去的好東西 🚀

二. 安裝
#

用 pip 安裝
#

pip install ruff

用 uv 安裝(推薦!)
#

如果你已經在用 uv,那就更簡單了:

uv tool install ruff

用 Homebrew(macOS)
#

brew install ruff

安裝完確認一下版本:

ruff --version
# ruff 0.9.x

三. 基本使用:Lint 檢查
#

最簡單的用法,對整個專案跑 lint:

ruff check .

看到類似這樣的輸出:

app/main.py:3:1: F401 [*] `os` imported but unused
app/utils.py:15:5: E711 Comparison to `None` (use `is None`)
Found 2 errors.
[*] 1 fixable with the `--fix` option.

自動修復
#

很多問題 Ruff 可以自動幫你修:

ruff check --fix .

它會幫你移除沒用到的 import、修正排序等等。加上 --unsafe-fixes 可以處理更多情況(但建議先看一下改了什麼)。

只看某個檔案
#

ruff check app/main.py

四. Format:取代 Black
#

從 Ruff 0.1 開始,內建了 formatter,可以直接取代 Black:

ruff format .

想看看它會改什麼但不要真的改:

ruff format --check .
# 或看 diff
ruff format --diff .

Ruff 的 formatter 基本上跟 Black 的風格一致(line length 預設 88),但速度快非常多。

五. 設定檔:pyproject.toml
#

Ruff 支援在 pyproject.tomlruff.toml.ruff.toml 裡設定。拍拍君推薦用 pyproject.toml,跟其他工具放在一起:

[tool.ruff]
# 每行最大長度
line-length = 100

# Python 版本(影響 pyupgrade 規則)
target-version = "py312"

[tool.ruff.lint]
# 啟用的規則集
select = [
    "E",    # pycodestyle errors
    "W",    # pycodestyle warnings
    "F",    # pyflakes
    "I",    # isort
    "N",    # pep8-naming
    "UP",   # pyupgrade
    "B",    # flake8-bugbear
    "SIM",  # flake8-simplify
    "RUF",  # Ruff 自己的規則
]

# 要忽略的規則
ignore = [
    "E501",   # line too long(讓 formatter 處理)
]

[tool.ruff.lint.isort]
known-first-party = ["my_package"]

[tool.ruff.format]
# 用雙引號(跟 Black 預設一樣)
quote-style = "double"

常用規則集一覽
#

代碼 來源 說明
E/W pycodestyle 基本風格檢查
F pyflakes 未使用 import、未定義變數
I isort import 排序
N pep8-naming 命名規範
UP pyupgrade 升級舊語法
B flake8-bugbear 常見 bug 模式
SIM flake8-simplify 簡化冗餘寫法
RUF Ruff Ruff 專屬規則
D pydocstyle docstring 風格
T20 flake8-print 偵測 print()

想看完整清單可以跑:

ruff rule --all | head -50

六. 整合 pre-commit
#

把 Ruff 加到 pre-commit 裡,每次 commit 自動檢查,再也不會忘記:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.9.6
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

安裝 hook:

pre-commit install

之後每次 git commit 都會自動跑 Ruff lint + format。如果有問題會擋住 commit,幫你守住程式碼品質 💪

搭配 CI/CD
#

在 GitHub Actions 裡也很簡單:

# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]

jobs:
  ruff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/ruff-action@v2
        with:
          args: "check"
      - uses: astral-sh/ruff-action@v2
        with:
          args: "format --check"

七. 實戰:從零設定一個專案
#

來看看拍拍君怎麼幫一個新專案設定 Ruff:

# 建立專案
mkdir pypy-demo && cd pypy-demo
uv init

先寫一個「有點亂」的 Python 檔:

# pypy_demo/main.py
import os
import sys
import json
from typing import Optional, List

def calculate_score(name:str,scores:list[int])->float:
    total=sum(scores)
    avg=total/len(scores)
    unused_var = "拍拍君到此一遊"
    if avg == None:
        return 0
    return avg

class chatPTT_analyzer:
    def __init__(self, data: Optional[dict] = None):
        self.data = data if data != None else {}

    def process(self):
        result = []
        for key in self.data.keys():
            if type(self.data[key]) == str:
                result.append(self.data[key])
        return result

跑一下 lint:

ruff check pypy_demo/main.py
pypy_demo/main.py:1:8: F401 [*] `os` imported but unused
pypy_demo/main.py:2:8: F401 [*] `sys` imported but unused
pypy_demo/main.py:3:8: F401 [*] `json` imported but unused
pypy_demo/main.py:4:29: F401 [*] `typing.List` imported but unused
pypy_demo/main.py:10:5: F841 Local variable `unused_var` is assigned to but never used
pypy_demo/main.py:11:11: E711 Comparison to `None` (use `is None`)
pypy_demo/main.py:15:7: N801 Class name should use CapWords convention
pypy_demo/main.py:17:31: E711 Comparison to `None` (use `is not None`)
pypy_demo/main.py:21:12: E721 Do not compare types, use `isinstance()`
Found 9 errors.
[*] 4 fixable with the `--fix` option.

一次抓出 9 個問題!用 --fix 先自動修掉能修的:

ruff check --fix pypy_demo/main.py

自動移除了沒用的 import,剩下的手動改一下就好。再跑 format:

ruff format pypy_demo/main.py

搞定,乾淨又整齊 ✨

八. 進階小技巧
#

忽略特定行
#

有時候某行就是需要長一點,或者有特殊理由:

import magic_module  # noqa: F401  ← 告訴 Ruff 這行不要管

忽略整個檔案
#

在設定檔裡排除:

[tool.ruff.lint]
exclude = ["migrations/", "legacy_code.py"]

輸出 JSON 格式
#

方便跟其他工具串接:

ruff check --output-format json .

看某個規則的說明
#

ruff rule E711

會顯示這個規則的詳細說明跟範例,很適合學習 🎓

結語
#

Ruff 真的是拍拍君近年最愛的 Python 工具之一。它把 linter、formatter、import sorter 全部整合在一起,而且速度快到誇張。

如果你還在用 flake8 + black + isort 的組合,拍拍君強烈建議試試看 Ruff——遷移成本很低,但效率提升非常有感。

一個工具取代一堆工具,快、準、穩。還有什麼好猶豫的呢?😎

延伸閱讀
#

Python 學習 - 本文屬於一個選集。
§ 15: 本文

相關文章

超快速 Python 套件管理:uv 完全教學
·6 分鐘· loading · loading
Python Uv Package Manager Rust
Python 資料驗證小幫手:Pydantic
·4 分鐘· loading · loading
Python Pydantic Data Validation
科學計算:數值積分
·5 分鐘· loading · loading
Python Numpy Scipy Numerical Methods Numerical Integral
讓你的終端機華麗變身:Rich 套件教學
·2 分鐘· loading · loading
Python Rich Cli
Python Typing:讓你的程式碼更安全、更好維護
·4 分鐘· loading · loading
Python Typing Type Hints
Python: 我需要進度條! tqdm
·3 分鐘· loading · loading
Python Tqdm Data Science