Python 标准规范 是做什么的?
本技能是 Python 工具链和跨项目规则的权威来源。架构、样式、测试和审查技能应引用本技能,而非重复其内容。
对于项目中的自然语言文档,请同时加载 technical-writing。它会调用 humanizer,并负责文档位置、时间戳、审查元数据、可访问性和写作质量。
规则优先级
按以下顺序应用规则:
- 用户或组织的显式要求
- 仓库配置和已接受的架构决策
- 本技能和其它 Python 技能
- PEP 8 和工具默认值
不要静默削弱安全基线。如需例外,须在项目的决策记录中记录其类型、范围、风险、已知负责人和移除条件。
运行时与布局
- 目标 Python 3.11 或更新版本。如果仓库定位的是更新版本,则尊重该版本。
- 当
from __future__ import annotations能提升前向兼容性或解决注解顺序问题时使用它。不要机械地将其添加到不需要的文件中。 - 对于新的可分发包,优先使用
src/布局。 - 将项目元数据和受支持工具配置保存在
pyproject.toml中。 - 使用
uv管理环境、依赖解析、锁定和命令执行。
受支持的工具链
| 用途 | 默认工具 | 是否必需 |
|---|---|---|
| 格式化和 lint | Ruff | 所有受维护的项目 |
| 类型检查 | mypy strict | 生产包和公共 API |
| 测试和覆盖率 | pytest、pytest-cov、coverage.py | 所有行为变更 |
| 安全 lint | Ruff S 规则和 Bandit | 生产包 |
| SAST | 带仓库规则的 Semgrep | 受维护产品的 CI |
| 依赖审计 | pip-audit | CI 和发布检查 |
| 结构化日志 | structlog | 应用和服务 |
| 追踪 | OpenTelemetry | 网络化服务和有追踪的流水线 |
| 包管理 | uv | 所有项目 |
| Git 钩子 | pre-commit | 受维护的仓库 |
| 机密交付 | 按组织默认为 Infisical | 使用机密的工作负载 |
已接受的架构决策可以选择等效的供应商,或为小型本地库或脚本省略某个运营工具。Ruff、类型检查、测试和安全依赖处理仍然是基线。
实验性工具(包括基于模型的漏洞扫描器和 AI 审查者)仅作为建议,不得成为强制门禁,除非团队定义了受支持的版本、命令、失败策略和负责人。
依赖与版本策略
- 在
pyproject.toml中声明兼容的运行时范围。 - 对于应用和可重现的开发环境,提交
uv.lock文件。锁定文件拥有确切的解析版本。 - 对于发布的库,声明诚实的下限,测试支持的 Python 版本和重要的下限,并且不要依赖锁定文件作为消费者契约。
- 将质量和测试工具放入命名的依赖组中。
- 通过锁定的 uv 环境运行质量命令。不要在 pre-commit 中维护第二套 Python 工具版本。
- 使用
uv lock --upgrade-package <name>或有审查的完整锁刷新进行有意升级。
uv 项目的规范依赖审计:
requirements_file="$(mktemp)"
trap 'rm -f "$requirements_file"' EXIT
uv export \
--frozen \
--no-dev \
--no-emit-project \
--no-hashes \
--format requirements.txt \
--output-file "$requirements_file"
uv run --frozen pip-audit \
--strict \
--no-deps \
--requirement "$requirements_file"
导出审计仅审计锁定的运行时依赖,无需 pip-audit 解析第二个图。当前 pip-audit --locked 项目发现支持 pyproject.toml 和 pylock.*.toml,但不支持 uv.lock,因此不要将 pip-audit --locked . 用于 uv 审计。
pip-audit 必须存在于质量依赖组中。漏洞豁免必须指明公告、受影响范围、补偿性控制、已知负责人以及到期或审查日期。
规范的质量命令
调整包路径以适配仓库,但保留一个命令源供本地使用和 CI:
uv sync --frozen
uv run --frozen ruff check .
uv run --frozen ruff format --check .
uv run --frozen mypy <package-roots>
uv run --frozen bandit -c pyproject.toml -r <package-roots>
uv run --frozen semgrep scan --config .semgrep.yml <package-roots>
uv run --frozen pytest
# 运行上面规范的导出锁定依赖审计。
CI 记录命令及其结果。缺少工具或配置为 unavailable,而非通过。
Pre-commit 所有权
按版本固定非 Python 的 hygiene 挂钩。将 Python 质量工具作为本地挂钩通过 uv run --frozen 调用,使本地挂钩和 CI 使用 uv.lock 中的确切版本。
repos:
- repo: local
hooks:
- id: ruff-check
name: ruff check
entry: uv run --frozen ruff check --fix
language: system
types_or: [python, pyi]
- id: ruff-format
name: ruff format
entry: uv run --frozen ruff format
language: system
types_or: [python, pyi]
- id: mypy
name: mypy
entry: uv run --frozen mypy
language: system
pass_filenames: false
args: [<package-roots>]
在 CI 中运行 Bandit、Semgrep、测试和依赖审计。仅在其延迟和依赖适合仓库时,才将其添加到 pre-commit。
最低 pyproject.toml 决策
使用仓库的包名和根目录,而非复制占位符。
[tool.ruff]
line-length = 88
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "B", "S", "UP", "C4", "SIM", "RUF", "D"]
ignore = ["E501"]
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.mypy]
python_version = "3.11"
strict = true
warn_return_any = true
warn_unused_configs = true
ignore_missing_imports = false
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra --strict-markers --tb=short --import-mode=importlib"
[tool.coverage.run]
branch = true
source_pkgs = ["your_package"]
[tool.coverage.report]
fail_under = 80
show_missing = true
[tool.bandit]
exclude_dirs = ["tests"]
skips = ["B101"]
为每个忽略项记录其狭窄范围和原因。优先使用基于文件的忽略而非全局忽略。
结构化日志契约
应用发出以下字段。库可以使用宿主应用的日志门面,并且不得在导入时配置全局处理器。
| 字段 | 要求 | 含义 |
|---|---|---|
timestamp | 始终 | ISO 8601 UTC |
level | 始终 | DEBUG、INFO、WARNING、ERROR 或 CRITICAL |
event | 始终 | 稳定的蛇形命名事件名称 |
service | 应用始终 | 服务或应用名称 |
trace_id | 存在追踪时 | OpenTelemetry 追踪 ID |
span_id | 存在跨度时 | OpenTelemetry 跨度 ID |
error | 失败时 | 安全的异常类型和消息 |
duration_ms | 测量操作时 | 经过的毫秒数 |
Structlog 的级别处理器通常接收小写的方法名。添加级别后将其标准化:
from typing import Any
import structlog
def uppercase_log_level(
_logger: Any,
_method_name: str,
event_dict: dict[str, Any],
) -> dict[str, Any]:
"""Normalize the structured log level to the shared schema."""
level = event_dict.get("level")
if isinstance(level, str):
event_dict["level"] = level.upper()
return event_dict
processors = [
structlog.processors.add_log_level,
uppercase_log_level,
structlog.processors.TimeStamper(fmt="iso", utc=True),
]
测试最终渲染的事件,而不仅仅测试处理器本身。
安全基线
绝对禁止:
- 提交或硬编码凭据、令牌、私钥或真实的秘密值
- 将真实秘密存储在
.env或 TOML 文件中 - 记录秘密、访问令牌、敏感个人数据或未限制的请求体
- 将外部值插入 SQL
- 对外部或可变输入使用
shell=True - 反序列化不受信任的 pickle 数据
- 对不受信任的输入使用
eval、exec或compile - 使用可变默认参数
- 使用通配符导入,除非是文档化的公共重新导出
- 静默丢弃安全敏感的返回值或异常
- 使用裸的
except:
仅在进程、工作器、任务或框架边界捕获 Exception,此时处理器安全地记录日志然后重新抛出、将失败映射到退出状态或返回已定义协议错误。在应用逻辑内捕获特定异常。
将普通设置和秘密引用存储在配置文件中。在启动时通过 Infisical 注入或批准的等效方式交付秘密值。在接受工作前验证必需的配置。