读书笔记 · 约 21,408 字 · 预计推送 3 部分

精读笔记:可维护 Python 项目模板 — uv / Ruff / Pyright / pytest / pre-commit(Windows 可复制配置) 未开始

目录

题记与说明


0. 七维总览

维度内容
核心问题Python 官方不统一项目工程化:环境、格式化、lint、类型、测试、提交门禁曾分散在 pip/virtualenv、Black+isort+Flake8+pyupgrade、mypy、unittest、手写 git hook 等一串工具里,新项目搭一套标准脚手架成本高,Windows 上还有安装与换行等额外摩擦
主要主张2025-2026 共识栈收敛为:uv(替代 pip/pipx/pyenv/virtualenv/poetry)+ Ruff(一个 Rust 工具替代 Black/isort/Flake8/pyupgrade 大部分)+ Pyright 或 mypy(类型)+ pytest(测试)+ pre-commit(把前四者挂到 git hook 上强制执行);全部工具配置可集中进一个 pyproject.toml
方法逐工具精读官方文档(uv projects/installation、Ruff tutorial、Pyright getting-started/configuration、pytest customize、pre-commit 全文、pre-commit-hooks README、pyright-python README)+ 一篇第三方工具链观点文(pydevtools.com);从官方文档提取可复制配置片段,再按 Windows 约束(PowerShell、CRLF、大小写不敏感、py launcher)重新拼装
数据与证据uv.lock 跨平台锁文件必须入库;uv run 每次先校验锁文件与环境同步;Ruff 900+ 规则、默认启用 F/E/B/UP/RUF;Pyright typeCheckingMode 默认 standard;pytest 配置文件"first match wins"永不合并;pre-commit 的 python hook 官方声明 tested on windows;Windows 专属 hook(check-case-conflict / check-illegal-windows-names / destroyed-symlinks)有官方定义
结论给出完整可复制模板(PowerShell 安装命令、pyproject.toml 全量、.pre-commit-config.yaml 全量、.gitattributes、命令速查);Windows 侧最大的坑是换行策略与 hook 的交互、pyright 在 pre-commit 独立 venv 里找不到依赖这两点,均有文档级解法
局限各 rev 版本号会过时;pre-commit pytest 钩子的 language: unsupported 写法需 pre-commit ≥4.4.0;Windows Store 版 Python 的 PATH 问题仅在搜索摘要中看到线索、未深读原帖;模板未在真实机器上端到端跑过(本子任务未创建 uv 项目实测)
对我的启发锁文件入库 + uv run 自动同步 = 新机器 5 分钟复现环境;pre-commit 只做"快门禁"(lint/format/类型),pytest 挂 pre-push,重测试留给 CI;Windows 团队第一天就该提交 .gitattributes,否则换行问题会被拖到协作中期

1. 来源清单(本次实际精读)

#来源抓取方式说明
1uv — Working on projects(页面发布 2026-10-09)WebReader 全文uv 项目工作流官方指南
2uv — Installation(页面发布 2026-10-21)WebReader 全文含 Windows PowerShell / winget / scoop 安装
3Ruff — TutorialWebReader 全文含规则选择、配置发现、ruff-pre-commit 集成
4Pyright — Getting StartedWebReader 全文类型检查渐进式采纳路径
5Pyright — ConfigurationWebFetch 摘要pyrightconfig.json / [tool.pyright] / venv 解析
6pyright-python READMEWebFetch 摘要pyright CLI 安装、pre-commit hook、venv 陷阱
7pre-commit 官方文档WebReader 全文设计理念、配置项全表、Windows 相关注记
8pytest — Configuration referenceWebFetch 摘要配置文件优先级、[tool.pytest.ini_options]
9pre-commit/pre-commit-hooks READMEWebFetch 摘要 + git ls-remoteWindows 相关 hook 原文定义
10Effective Python Developer Tooling (Dec 2024), pydevtools.comWebFetch 摘要第三方观点佐证工具收敛趋势

辅助检索(未深读、仅作为线索标注):WebSearch 结果中提到的 MicroPython GitHub Discussion #12045(Windows 上 pre-commit 装不上)、Reddit r/Python "Ultra-strict Python template v2"(uv + basedpyright 超严格模板)。


2. 核心问题

这份"模板"在解决什么问题?综合 pydevtools.com 与各官方文档:

  1. 工具链碎片化:传统 Python 项目要装 pip、virtualenv、pip-tools、pipx、pyenv、Black、isort、Flake8、pyupgrade、mypy……pydevtools.com 直接说 uv "replac[es] pip, pip-tools, pipx, poetry, pyenv, twine, and virtualenv",Ruff 是 "linter/formatter replacing Black, flake8 (mostly), and isort"。
  2. 约束不自动执行:lint/格式/类型规则写在文档里没人遵守;pre-commit 官网对动机的原文:"We run our hooks on every commit to automatically point out issues in code such as missing semicolons, trailing whitespace, and debug statements. By pointing these issues out before code review, this allows a code reviewer to focus on the architecture of a change while not wasting time with trivial style nitpicks."
  3. 跨机器可复现:uv 用锁文件解决;pytest/pyright/Ruff 配置集中到 pyproject.toml 解决"每台机器配置漂移"。
  4. Windows 额外摩擦:安装脚本、venv 激活路径、py launcher、大小写不敏感文件系统、CRLF 换行——官方文档各有零散注记,本笔记把它们收拢成清单。

3. 主要主张(逐来源)

3.1 uv:一个二进制管完环境与依赖

"uv.lock is a cross-platform lockfile that contains exact information about your project's dependencies. … This file should be checked into version control, allowing for consistent and reproducible installations across machines."
"Prior to every uv run invocation, uv will verify that the lockfile is up-to-date with the pyproject.toml, and that the environment is up-to-date with the lockfile, keeping your project in-sync without the need for manual intervention."
  PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

并有注记 "Changing the execution policy allows running a script from the internet.";winget 原文:winget install --id=astral-sh.uv -e。

3.2 Ruff:lint + format 合一,默认规则就是起点

  - repo: https://github.com/astral-sh/ruff-pre-commit
    # Ruff version.
    rev: v0.17.2
    hooks:
      # Run the linter.
      - id: ruff-check
      # Run the formatter.
      - id: ruff-format

3.3 Pyright:渐进式类型检查,standard 起步

"On a file-by-file basis, enable all type checking options by adding the comment # pyright: strict somewhere in the file."

3.4 pyright-python:CLI 与 pre-commit hook 的官方接入点

  repos:
  -   repo: https://github.com/RobertCraigie/pyright-python
      rev: v1.1.414
      hooks:
      -   id: pyright
  [tool.pyright]
  venvPath = "."
  venv = ".venv"

3.5 pytest:配置进 pyproject.toml,优先级"先到先得"

  [tool.pytest.ini_options]
  minversion = "6.0"
  addopts = "-ra -q"
  testpaths = [
      "tests",
      "integration",
  ]

3.6 pre-commit:多语言 hook 包管理器

3.7 pre-commit-hooks:Windows 专属防线(官方仓库)

来源 9 原文定义,逐条摘录:

hook官方原文/摘要
check-case-conflict"Check for files with names that would conflict on a case-insensitive filesystem like MacOS HFS+ or Windows FAT."
check-illegal-windows-names"flags files that cannot be created on Windows"(如 aux.py、con.py 等保留名)
destroyed-symlinks捕捉"符号链接退化成内容为指向路径的普通文件"——"a common problem on Windows"
mixed-line-ending"Replaces or checks mixed line ending.";强制替换为 LF 时官方警告 "This option isn't compatible with git setup check-in LF check-out CRLF",因为 git smudge 会在 hook 跑完后又把换行改回去
end-of-file-fixer"Makes sure files end in a newline and only a newline."
check-toml / check-yaml"Attempts to load all TOML/yaml files to verify syntax."

3.8 第三方观点佐证(pydevtools.com, Tim Hopper, 2024-12)


4. Windows 专属要点汇总(全部有出处)

  1. uv 安装:PowerShell 用 irm … install.ps1 | iex(配 -ExecutionPolicy ByPass),或 winget install --id=astral-sh.uv -e,或 Scoop(来源 2)。
  2. venv 激活:PowerShell 下 .venv\Scripts\activate(来源 1/3 原文给出该路径)。若遇脚本执行策略限制,官方 uv 安装脚本自身的处理方式就是 -ExecutionPolicy ByPass;venv 激活脚本被拦时的通用解法 Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser 属于社区常识,未在本次抓取页面中验证,特此标注。
  3. uv run 优先于激活:Windows 上最容易踩的就是"激活失败就放弃"。文档语义(来源 1)保证 uv run 每次自动校验并同步环境,等价于"永远在正确的 venv 里"。模板的 hook entry 全部用 uv run 前缀正是基于此。
  4. py launcher:pre-commit 的 language_version 在 Windows 上自动把 python3.x 翻译成 py -3.x 调用(来源 7 原文),所以配置里照写 python3.12 即可,不要写 Windows 特有写法。
  5. 大小写不敏感文件系统:加 check-case-conflict(来源 9);Git for Windows 默认 core.ignorecase=true,文件名大小写冲突在 Windows 上无感、推到 Linux CI 才炸。
  6. Windows 保留文件名:加 check-illegal-windows-names(来源 9),防 aux.py/con.py 这类在 Windows 上根本建不出来的文件名入库。
  7. 符号链接退化:Windows 上 clone 含 symlink 的仓库常见"链接变文本文件",加 destroyed-symlinks(来源 9)。
  8. 换行(最大的坑):来源 9 明确警告 mixed-line-ending 强制替换与 "check-in LF check-out CRLF" 的 git 配置互斥。工程上稳定的组合是:仓库提交 .gitattributes 统一 eol=lf(文本文件),.bat/.cmd/.ps1 保留 CRLF——这是我的综合建议(文档只给了警告与机制,未给完整 .gitattributes),并在模板第 6 节给出。
  9. init-templatedir:PowerShell 下 pre-commit init-templatedir $HOME\.git-template(来源 7 原文),想让所有新 clone 的仓库自动带 hook 时用。
  10. pyright 的 Node 依赖:Windows 上若没装 Node,pyright CLI 会现场下载(来源 6);国内网络环境建议 pip install pyright[nodejs](nodejs-wheel 走 pip 源,比 nodeenv 稳)或预先装 Node,并用 PYRIGHT_PYTHON_CACHE_DIR 等变量控制缓存位置。
  11. Windows Store Python 的 PATH 问题:WebSearch 摘要显示有"Store 版 Python 3.11 + pre-commit 3.3.3 装不上 hook"的社区讨论(来源线索 10),未深读原帖;规避方式是安装 python.org 发行版或干脆全用 uv 管理的 Python(uv python install),避免 PATH 上多解释器打架。

5. 结论:可复制模板(Windows / PowerShell)

使用方式:逐块复制。所有命令在 PowerShell 下运行。版本号截至 2026-10-31,rev 以各仓库最新 tag 为准(pre-commit autoupdate 可自动升级)。

5.1 安装工具链(一次性)

# uv:三选一
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
winget install --id=astral-sh.uv -e
scoop install uv

# (可选)uv 的 PowerShell 补全
if (!(Test-Path -Path $PROFILE)) { New-Item -ItemType File -Path $PROFILE -Force }
Add-Content -Path $PROFILE -Value '(& uv generate-shell-completion powershell) | Out-String | Invoke-Expression'

5.2 初始化项目

uv init myproj
cd myproj
# uv init 已生成:pyproject.toml、README.md、.python-version、.gitignore、src/myproj/__init__.py

# dev 依赖一把装齐(写入 [dependency-groups] dev 组并更新 uv.lock)
uv add --dev pytest pytest-cov pre-commit pyright

5.3 pyproject.toml(在 uv init 产物上追加)

[project]
name = "myproj"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
# uv init 默认给 ">=3.14";团队要兼容旧机可自行下调(我的取舍,非文档默认)
requires-python = ">=3.12"
dependencies = []

# ---- Ruff:lint + format ----
[tool.ruff]
line-length = 100
target-version = "py312"

[tool.ruff.lint]
# 官方默认 F/E/B/UP/RUF 之外只加 I(isort 等价物);需要更严再加 D(pydocstyle)
select = ["E", "F", "B", "UP", "RUF", "I"]

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

# ---- Pyright:类型检查(也可拆成单独 pyrightconfig.json,优先级更高) ----
[tool.pyright]
include = ["src", "tests"]
pythonVersion = "3.12"
typeCheckingMode = "standard"        # off/basic/standard/strict,默认 standard
venvPath = "."                        # pyright-python README 对 pre-commit 场景的推荐写法
venv = ".venv"                        # uv 的 venv 固定在项目根,全队路径一致

# ---- pytest ----
[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests"]
addopts = "-ra --strict-markers"      # --strict-markers 防止自定义 marker 拼错静默通过
markers = [
    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
]
# filterwarnings = ["error"]          # 严格团队再开:警告即失败

# ---- 依赖组(uv add --dev 会自动维护) ----
[dependency-groups]
dev = [
    "pytest>=8.3",
    "pytest-cov>=5.0",
    "pre-commit>=4.0",
    "pyright>=1.1.414",
]

5.4 .pre-commit-config.yaml(项目根,全新文件)

# pre-commit >= 4.4.0 才支持 language: unsupported;旧版请写 language: system
default_install_hook_types: [pre-commit, pre-push]
default_language_version:
  # Windows 上 pre-commit 会自动翻译成 py -3.12 调用(官方文档原文)
  python: python3.12

repos:
  # ① 通用卫生检查(Windows 防线 + 文件尾/空白)
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0            # 2026-10-31 经 git ls-remote 核实的最新 tag
    hooks:
      - id: check-case-conflict        # 大小写冲突(Windows/FAT 无感、CI 才炸)
      - id: check-illegal-windows-names # Windows 保留文件名
      - id: destroyed-symlinks          # 符号链接退化检测
      - id: check-toml
      - id: check-yaml
      - id: end-of-file-fixer
      - id: trailing-whitespace

  # ② Ruff:lint(带自动修复)+ format
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.17.2           # Ruff 官方教程给出的 rev
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format

  # ③ Pyright:类型检查(读 pyproject.toml 的 [tool.pyright])
  - repo: https://github.com/RobertCraigie/pyright-python
    rev: v1.1.414          # pyright-python README 示例 rev
    hooks:
      - id: pyright

  # ④ pytest:挂 pre-push,不拖慢每次 commit(本模板的综合取舍)
  - repo: local
    hooks:
      - id: pytest
        name: pytest
        entry: uv run pytest    # uv run 保证锁文件与环境同步后再跑测试
        language: unsupported   # pre-commit <4.4.0 写 language: system
        pass_filenames: false
        always_run: true
        stages: [pre-push]

启用与首次全量跑:

uv run pre-commit install        # 按 default_install_hook_types 装 pre-commit + pre-push 两个钩子
uv run pre-commit run --all-files # 存量代码先全量过一遍;改文件类 hook 失败后 git add 再跑
uv run pre-commit autoupdate      # 日后一键升级所有 rev

5.5 .gitattributes(项目根,全新文件;Windows 团队第一天就提交)

# 文本文件统一 LF 入库;脚本类保留 CRLF(综合建议:依据 mixed-line-ending 的
# 官方警告 "isn't compatible with git setup check-in LF check-out CRLF" 推出)
* text=auto
*.py   text eol=lf
*.toml text eol=lf
*.yaml text eol=lf
*.yml  text eol=lf
*.md   text eol=lf
*.bat  text eol=crlf
*.cmd  text eol=crlf
*.ps1  text eol=crlf

5.6 命令速查(PowerShell)

uv sync                  # 按 uv.lock 精确还原环境(clone 后第一步)
uv add requests          # 加运行依赖;uv add --dev pytest 加开发依赖
uv run myproj            # 运行项目入口(自动校验锁文件+环境)
uv run pytest            # 跑测试
uv run ruff check --fix && uv run ruff format   # 手动 lint+format
uv run pyright           # 手动类型检查
uv run pre-commit run --all-files                 # 全量过门禁
uv lock --upgrade-package requests               # 定点升级某依赖
uv build                  # 构建 sdist/wheel 到 dist/
SKIP=pytest git commit -m "wip"  # 临时跳过某个 hook(比 --no-verify 精细)

6. 局限(作者承认的 + 我推断的)

  1. rev 会过时:v0.17.2(ruff-pre-commit)、v1.1.414(pyright-python)、v6.0.0(pre-commit-hooks)都是抓取时点的值;官方文档自己也在 yaml 注释里写 "Ruff version."——模板必须配合 pre-commit autoupdate 使用。
  2. 端到端未实测:本子任务只做了文档精读与 git ls-remote 核实,没有在真实 Windows 机器上执行 uv init → pre-commit run --all-files 全流程验证模板(无法在本环境中创建 uv 项目实测),模板中标注"综合建议/取舍"的块(.gitattributes、pytest 挂 pre-push、line-length=100)是经验性配置而非文档原文。
  3. Windows Store Python 问题未深读:检索摘要显示存在 Store 版 Python + pre-commit 的兼容问题讨论,但原帖未精读,只能给出规避建议而非确认性结论。
  4. pytest hook 的 language: unsupported:pre-commit 4.4.0 才引入该名字(官方注记 "previously language: system. the alias will be removed in a future version"),旧版本 pre-commit 会报不认识的 language。
  5. mypy vs Pyright 无定论:pydevtools.com 用 mypy、Reddit 共识是"二选一",Reddit 检索摘要明确提到 "running pyright *and* mypy is often overkill";本模板选 Pyright 是工程取舍(与 Pylance 同引擎),非社区唯一答案。
  6. pre-commit 不是 CI 的替代:官方文档明确 "consider running that in CI too"——hook 只守本地提交,CI 仍需 pre-commit run --all-files + uv run pytest 作为最终门禁。
  7. uv 默认 Python 版本激进:抓取的 uv 文档中 uv init 默认 requires-python = ">=3.14",对存量 Windows 环境(公司里常见 3.10/3.11)偏激进,模板中手动下调到 3.12,需按团队实情改。

7. 对我的启发(可迁移原则)

  1. "配置集中 + 锁文件入库 + 自动同步入口"三件套是可维护性的最小闭环:uv.lock 入库(文档明说跨机器复现)+ uv run 自动校验同步 + 所有工具配置收进一个 pyproject.toml——新机器从 clone 到可跑测试只剩两条命令(uv sync、uv run pytest)。我自己的项目脚本/工具链迁移时直接照此模式。
  2. 门禁分层:快的挂 pre-commit,慢的挂 pre-push,最重的留 CI:lint/format/类型是秒级,挂 pre-commit;pytest 分钟级,挂 pre-push 或 CI。不要把全量测试塞进每次 commit——这是"规则自动执行"与"开发流畅度"的平衡点。
  3. Windows 不该是二等公民,但有四道专属防线:check-case-conflict、check-illegal-windows-names、destroyed-symlinks、.gitattributes 换行策略——前三个是现成 hook,第四个必须第一天提交,否则 CRLF 问题会在第一个协作者加入时爆发。
  4. 包装器工具的"独立 venv 陷阱"具有普适性:pyright-python 在 pre-commit 的独立 venv 里看不到项目依赖(README 原文承认),解法是显式指回项目 venv(venvPath/venv)。凡是用 pre-commit 挂"需要项目环境上下文"的工具(pylint 同理,官方 local hooks 一节也点名了),都要检查它的 import 解析路径。
  5. 模板是负债不是资产:"Building and maintaining a robust template requires considerable effort."(pydevtools.com)——模板里的每个 rev 都是会腐烂的引用,pre-commit autoupdate + uv lock --upgrade 应写进团队例行操作,而不是等报错才升级。
  6. 写"可复制配置"时,每个版本号都要给核实方式:本次笔记对四个 rev/版本分别标注了出处(官方页面原文 / git ls-remote 实测),并统一加"过时风险"提示——这比给一堆无出处的版本号可信得多。
← 资料库今日播报 →