把 Python 代码质量变成可测量的工程习惯

2026-06-30 22 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:7 分钟

Python 写起来快,但项目一大,问题也会来得快:格式不统一、隐藏的类型错误、重复逻辑、慢查询、慢循环、没人敢改的函数。代码质量不是“感觉还行”,而是一组可以持续执行、可以在 CI 里拦截、可以用数据观察的工程实践。

这篇文章围绕 Python 代码质量的几类核心工具展开:格式化器、Linter、类型检查器、测试覆盖率工具和性能分析器。它们不应该互相替代,而应该像仪表盘一样一起工作。

质量工具各管一段路

Python 代码质量通常可以拆成几层:

  • 格式一致性:交给 blackruff format 这类格式化工具,减少代码审查里的风格争论。
  • 静态问题扫描:交给 ruffflake8pylint 等 Linter,提前发现未使用变量、复杂表达式、潜在 bug。
  • 类型正确性:交给 mypypyright,让接口约定更清楚,尤其适合多人协作和大型代码库。
  • 行为正确性:交给 pytest 和覆盖率工具,验证代码是否按预期运行。
  • 性能瓶颈定位:交给 cProfilepy-spyscalene 等 profiler,别靠猜测优化。

关键点是:不要把所有问题都塞给一个工具。格式工具负责“长得一致”,Linter 负责“明显不对”,类型检查负责“接口契约”,测试负责“行为可信”,Profiler 负责“慢在哪里”。

一个可以直接改造的最小项目配置

下面是一个小型 Python 项目的实用配置。你可以把它复制到现有项目中,再根据团队习惯调整规则。

pyproject.toml

[project]
name = "quality-demo"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = []

[project.optional-dependencies]
dev = [
  "ruff>=0.6.0",
  "mypy>=1.10.0",
  "pytest>=8.0.0",
  "coverage>=7.5.0"
]

[tool.ruff]
line-length = 100
target-version = "py311"

[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP"]
ignore = []

[tool.mypy]
python_version = "3.11"
strict = true
warn_unused_ignores = true
warn_return_any = true

[tool.pytest.ini_options]
testpaths = ["tests"]

[tool.coverage.run]
branch = true
source = ["src"]

[tool.coverage.report]
show_missing = true
fail_under = 85

创建一个示例函数:

# src/pricing.py
from decimal import Decimal


def apply_discount(price: Decimal, percent: Decimal) -> Decimal:
    if percent < 0 or percent > 100:
        raise ValueError("percent must be between 0 and 100")
    return price * (Decimal("1") - percent / Decimal("100"))

再写一个测试:

# tests/test_pricing.py
from decimal import Decimal

import pytest

from pricing import apply_discount


def test_apply_discount() -> None:
    assert apply_discount(Decimal("100"), Decimal("15")) == Decimal("85.00")


def test_reject_invalid_discount() -> None:
    with pytest.raises(ValueError):
        apply_discount(Decimal("100"), Decimal("120"))

本地运行命令:

python -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'

ruff format .
ruff check .
mypy src tests
coverage run -m pytest
coverage report

如果你的项目没有使用 src 布局,需要把 source = ["src"] 和导入路径改成自己的目录,比如 apppackage_name

从“能跑”到“可维护”,差别在度量

代码质量管理最容易失败的地方,是只在口头上要求“写干净一点”。更有效的做法是给每一类质量问题一个可执行的检查点。

例如:

ruff check . --output-format=github
mypy src
coverage run -m pytest
coverage report --fail-under=85
python -m cProfile -o profile.out scripts/import_orders.py

这些命令分别回答不同问题:

  • ruff check:有没有低成本就能发现的代码问题?
  • mypy:函数参数、返回值、对象属性是否符合约定?
  • coverage report:测试是否覆盖了关键分支?
  • cProfile:慢到底慢在哪个函数?

注意,覆盖率不是质量本身。85% 覆盖率的垃圾测试,仍然保护不了重构。覆盖率更像烟雾报警器:数字太低通常危险,数字很高也不代表没有火灾。

性能也属于代码质量

很多团队把性能优化放在最后,直到用户投诉才开始看日志。更稳妥的做法是:在可疑路径上保留可重复的 profiling 入口。

可以这样实践:

# scripts/profile_pricing.py
from decimal import Decimal
from pricing import apply_discount


def main() -> None:
    total = Decimal("0")
    for index in range(100_000):
        total += apply_discount(Decimal(index), Decimal("12.5"))
    print(total)


if __name__ == "__main__":
    main()

运行:

python -m cProfile -s cumulative scripts/profile_pricing.py

-s cumulative 会按累计耗时排序,适合找“这个函数自己不慢,但它调用的一串东西很慢”的问题。真正优化前,先保存 profiling 结果;优化后再跑一次。没有对比数据的性能优化,经常只是把代码改复杂。

落地时别一次性拉满

如果是老项目,不建议第一天就开启最严格的规则并要求全量通过。更现实的路径是:

  • 先启用格式化器,让代码风格稳定下来。
  • 再启用 Linter,只拦截新代码里的明确问题。
  • 类型检查从核心模块开始,不要一口气覆盖所有历史代码。
  • 覆盖率门槛先设在当前水平附近,再逐步抬高。
  • Profiler 用在关键路径,不要为了“看起来专业”到处测。

好的 Python 代码质量体系,不是工具列表越长越好,而是每个工具都能在正确时间给出清晰信号。格式化减少噪音,Lint 提前发现低级错误,类型检查稳住接口,测试保护行为,性能分析阻止盲目优化。把这些检查放进日常开发和 CI,代码质量才会从个人习惯变成团队能力。


相关推荐