在昨天(Day 8),我們用 Hatch scripts × Nox 建立了一鍵化工作流。
從此以後,團隊成員不用記一堆零碎指令,只要一個 hatch run ci
就能把測試、型別檢查、lint 全部跑完。
但在某次 code review,卻還是吵了起來:
"
,有人用 '
。問題不是程式能不能跑,而是大家浪費太多時間在「風格爭論」上。
這正是今天要解決的主題:程式碼風格自動化。
👉 結論:交給工具,不要交給人。
I
規則取代。git commit
前自動執行,把關入口。所有規則都放進 pyproject.toml
,延續 Day 3 的「單一事實來源」理念。
pyproject.toml
裡設定[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
[tool.isort]
profile = "black"
line_length = 100
known_first_party = ["your_project"]
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:
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檢查
[tool.hatch.envs.default.scripts]
fmt = ["black .", "ruff --fix ."]
lint = ["ruff ."]
check = ["black --check .", "ruff ."]
使用方式:
hatch run fmt # 自動修正
hatch run check # 只檢查,不修改
# 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"
E203
。isort .
,再逐步切換到 Ruff 的 I
規則。到這裡,我們已經完成了「程式風格自動化」的最後一塊拼圖:
從此以後,團隊再也不用為了空格、引號或 import 排序爭論,大家能把精力留給設計與邏輯。
這正是工程化 Python 的精神:減少雜訊,聚焦價值 🚀。
下一篇(Day 10),我們將進一步探討 型別與資料契約,讓正確性在程式執行前就被檢查出來