iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0

在昨天(Day 8),我們用 Hatch scripts × Nox 建立了一鍵化工作流。

從此以後,團隊成員不用記一堆零碎指令,只要一個 hatch run ci 就能把測試、型別檢查、lint 全部跑完。

但在某次 code review,卻還是吵了起來:

  • 有人用 ",有人用 '
  • A 習慣 80 字換行,B 覺得 120 才剛好。
  • import 排序亂七八糟,PR diff 裡充滿無關差異。

問題不是程式能不能跑,而是大家浪費太多時間在「風格爭論」上。

這正是今天要解決的主題:程式碼風格自動化


為什麼要統一風格?

  • 減少無意義差異:PR diff 乾淨,reviewer 專注在邏輯。
  • 降低心智負擔:不用記住規範,交給工具自動處理。
  • 新手友善:pre-commit 幫忙修正,不怕踩雷。

👉 結論:交給工具,不要交給人


我們的工具組合

  • Black:唯一 formatter,專注排版。
  • Ruff:超快 linter,整合 flake8/pyflakes/pycodestyle/isort 功能,可自動修正。
  • isort(可選):傳統 import 排序器;若團隊已習慣可保留,否則讓 Ruff 的 I 規則取代。
  • pre-commit:在 git commit 前自動執行,把關入口。

所有規則都放進 pyproject.toml,延續 Day 3 的「單一事實來源」理念。


pyproject.toml 裡設定

Black + Ruff(建議組合)

[tool.black]
line-length = 100
target-version = ["py312"]

[tool.ruff]
line-length = 100
target-version = "py312"
select = ["E", "F", "I", "B", "UP", "SIM"]
ignore = ["E203"]  # 與 Black 衝突的 slicing 規則
exclude = ["build", "dist", ".venv"]

[tool.ruff.lint.isort]
known-first-party = ["your_project"]
combine-as-imports = true
force-sort-within-sections = true

如果要保留 isort

[tool.isort]
profile = "black"
line_length = 100
known_first_party = ["your_project"]


pre-commit 把關

常見寫法(直接跑工具)

repos:
  - repo: https://github.com/psf/black
    rev: 24.8.0
    hooks:
      - id: black

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.6.2
    hooks:
      - id: ruff
        args: ["--fix"]

  # 如果要保留 isort
  # - repo: https://github.com/pycqa/isort
  #   rev: 5.13.2
  #   hooks:
  #     - id: isort

進階寫法(呼叫 Hatch scripts)

如果你要「只維護一份規則」,可以改成呼叫 Hatch 的 scripts:

repos:
  - repo: local
    hooks:
      - id: hatch-fmt
        name: hatch fmt
        entry: hatch
        language: system
        args: ["run", "fmt"]
        types: [python]
        pass_filenames: false

      - id: hatch-check
        name: hatch check
        entry: hatch
        language: system
        args: ["run", "check"]
        types: [python]
        pass_filenames: false

這樣 pre-commit、Hatch、Nox、CI/CD 都用同一份設定。

⚠️ 注意:pass_filenames: false 代表每次 commit 都會檢查整個專案,效能差一點,但更一致。

當我們在本機執行git commit指令時就會觸發pre-commit檢查

https://ithelp.ithome.com.tw/upload/images/20250923/2017811709fO9MdMCf.png


與 Hatch scripts 整合

[tool.hatch.envs.default.scripts]
fmt = ["black .", "ruff --fix ."]
lint = ["ruff ."]
check = ["black --check .", "ruff ."]

使用方式:

hatch run fmt     # 自動修正
hatch run check   # 只檢查,不修改

與 Nox 整合(最終版 noxfile.py)


# noxfile.py
import nox

# 我們用 Hatch 當單一環境來源,所以不要求系統一定要有多版本直譯器
nox.options.error_on_missing_interpreters = False

@nox.session(name="fmt", venv_backend="none")
def fmt(session: nox.Session) -> None:
    """
    Auto-format code using the *current* Hatch env.
    - Black: format
    - Ruff:  auto-fix (含 import 排序 I 規則,若你改用 isort,這裡請調整)
    """
    session.run("black", ".")
    session.run("ruff", "check", "--fix", ".")

@nox.session(name="style", venv_backend="none")
def style(session: nox.Session) -> None:
    """
    Style-only checks (no mutation) using current Hatch env.
    - Black: --check
    - Ruff:  check (不自動修)
    """
    session.run("black", "--check", ".")
    session.run("ruff", "check", ".")

@nox.session(name="lint", venv_backend="none")
def lint(session: nox.Session) -> None:
    """
    Full static checks (style + type) using current Hatch env.
    - Black: --check
    - Ruff:  check
    - mypy:  type checks (可依專案調整路徑)
    """
    session.run("black", "--check", ".")
    session.run("ruff", "check", ".")
    session.run("mypy", "src/")

@nox.session(name="tests-3.11", venv_backend="none")
def tests_311(session: nox.Session) -> None:
    """
    Run tests (labelled 3.11) using the CURRENT env.
    建議在 CI job 裡用 setup-python matrix 指定 3.11 再呼叫此 session。
    """
    session.run("pytest", "-q")

@nox.session(name="tests-3.12", venv_backend="none")
def tests_312(session: nox.Session) -> None:
    """
    Run tests (labelled 3.12) using the CURRENT env.
    建議在 CI job 裡用 setup-python matrix 指定 3.12 再呼叫此 session。
    """
    session.run("pytest", "-q")

👉 用法建議:

  • 本地自動修:nox -s fmt

  • CI 檢查:nox -s style -s lint -s tests-3.12

  • Hatch 一鍵入口:

    # 只跑檢查和一個版本的測試
    [tool.hatch.envs.default.scripts]
    ci = "nox -s style -s lint -s tests-3.12"
    
    #執行所有的
    ci = "nox"
    

常見問題與解法

  • Black 與 Ruff 衝突 → 忽略 Ruff 的 E203
  • 導入初期改動巨大 → 建議分兩步:先全專案格式化,再啟用 pre-commit。
  • 既有 isort 配置 → 先跑一次 isort .,再逐步切換到 Ruff 的 I 規則。

結語

到這裡,我們已經完成了「程式風格自動化」的最後一塊拼圖:

  • Black → 排版
  • Ruff → 檢查
  • pre-commit → 守門
  • Hatch × Nox → 一鍵整合

從此以後,團隊再也不用為了空格、引號或 import 排序爭論,大家能把精力留給設計與邏輯。

這正是工程化 Python 的精神:減少雜訊,聚焦價值 🚀。

下一篇(Day 10),我們將進一步探討 型別與資料契約,讓正確性在程式執行前就被檢查出來


上一篇
Day 8 - 一鍵化開發工作流:Hatch scripts × Nox
下一篇
Day 10 -型別與資料契約:mypy / pyright 與 Pydantic v2
系列文
30 天 Python 專案工坊:環境、結構、測試到部署全打通11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言