ruff 与 mypy:代码质量 (Code Quality)


章节概述

C 语言项目中,代码质量由 clang-format(格式化)、clang-tidy(静态分析)、cppcheck(深度检查)、-Wall -Wextra(编译警告)共同保证。Python 的”编译器”(解释器)没有 -Wall 这类编译时检查——类型错误在运行时才会暴露。因此 Python 需要专门的工具来填补这个空白:ruff(闪电般的 linter + formatter,替代 flake8/isort/black)和 mypy(静态类型检查器,让 Python 也拥有类似 C 的类型安全)。本章从 C 程序员视角讲解 Python 代码质量工具的配置与使用。

核心理念:C 的编译警告在编译时告诉你”可能有问题”,Python 没有编译步骤,但 ruff 和 mypy 填补了这道防线。ruff 相当于 clang-format + clang-tidy + -Wall 的结合体,mypy 相当于给动态类型的 Python 加上了”静态类型 lint”——在你运行代码之前就发现类型错误。


第一节:ruff —— Python 的瑞士军刀

1.1 安装与首次使用

# 安装
pip install ruff
 
# 或使用 uv
uv pip install ruff
 
# 检查代码
ruff check myfile.py
 
# 自动修复
ruff check --fix myfile.py
 
# 格式化代码(替代 black)
ruff format myfile.py

1.2 ruff 替代了什么?

在 ruff 出现之前,Python 项目的质量工具链通常包含 4-5 个独立工具:

旧工具功能ruff 替代
flake8lint 检查ruff check
isortimport 排序ruff check --fix(I 规则)
pyflakes错误检测ruff check(F 规则)
pycodestylePEP 8 风格ruff check
black代码格式化ruff format

与 C 对比:C 语言中 clang-tidy 和 clang-format 是两个独立工具,ruff 将其统一为一个命令。从工程角度看,一个工具维护比 5 个工具组合更可靠。

1.3 内置规则集

# 查看所有可用规则
ruff linter
 
# 查看当前启用的规则
ruff check --show-settings

常用规则前缀:

前缀含义示例
E / Wpycodestyle 错误/警告行太长、多余空格
FPyflakes 错误检测未定义变量、未使用导入
Iisort 导入排序import 顺序混乱
Npep8-naming 命名约定类名应用 CamelCase
Bflake8-bugbear 常见 bug可变默认参数
SIMflake8-simplify 简化建议可简化的条件表达式
UPpyupgrade 语法升级使用现代 Python 语法
C4flake8-comprehensions推导式建议

1.4 pyproject.toml 配置

[tool.ruff]
line-length = 100
target-version = "py39"
 
[tool.ruff.lint]
# 选择要启用的规则集
select = [
 "E", # pycodestyle 错误
 "F", # Pyflakes
 "I", # isort
 "N", # pep8-naming
 "W", # pycodestyle 警告
 "B", # flake8-bugbear
 "SIM", # 简化建议
 "UP", # 语法升级
]
 
# 忽略特定规则
ignore = [
 "E501", # 行太长(由 formatter 处理)
]
 
[tool.ruff.lint.isort]
# import 排序规则
known-first-party = ["myproject"]
 
[tool.ruff.format]
# 格式化选项
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"

1.5 实战演示

# before.py — 充满问题的代码
import os, sys, json
import datetime
 
def my_function( x,y ):
 unused_var = 42
 result=x+y
 return result
 
class myclass:
 pass
 
MyVariable = "should be lowercase"
ruff check before.py

输出:

before.py:1:1: I001 Import block is un-sorted or un-formatted
before.py:1:8: F401 [*] `os` imported but unused
before.py:1:12: F401 [*] `json` imported but unused
before.py:2:1: F401 [*] `datetime` imported but unused
before.py:4:5: E271 multiple spaces after keyword
before.py:4:22: E231 missing whitespace after ','
before.py:5:4: F841 [*] Local variable `unused_var` is assigned to but never used
before.py:9:6: N801 Class name `myclass` should use CapWords convention
before.py:13:0: N816 Variable `MyVariable` should be lowercase

自动修复:

ruff check --fix before.py

修复后 (before.py):

import sys
 
 
def my_function(x, y):
 result = x + y
 return result
 
 
class Myclass:
 pass
 
 
my_variable = "should be lowercase"
ruff format before.py # 进一步格式化缩进和换行

第二节:mypy —— 静态类型检查

2.1 为什么 Python 需要类型检查?

# C 语言 —— 编译时就能发现类型错误
# int add(int a, int b) { return a + b; }
# add("hello", 42); // 编译错误!
 
# Python —— 运行时才报错
def add(a, b):
 return a + b
 
result = add("hello", 42)
# TypeError: can only concatenate str (not "int") to str
# 这行报错可能在生产环境中才触发!

mypy 让你在不运行代码的情况下发现类型错误:

def add(a: int, b: int) -> int:
 return a + b
 
add("hello", 42) # mypy 会在 "编译" 时报告错误:
# error: Argument 1 to "add" has incompatible type "str"; expected "int"

2.2 类型注解语法

# 基本类型
name: str = "Alice"
age: int = 30
price: float = 99.99
is_valid: bool = True
 
# 容器类型(Python 3.9+)
names: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {"Alice": 95}
point: tuple[float, float] = (3.0, 4.0)
unique_ids: set[int] = {1, 2, 3}
 
# 可选类型
from typing import Optional
def find_user(id: int) -> Optional[str]:
 users = {1: "Alice"}
 return users.get(id) # 可能返回 None
 
# Union 类型
from typing import Union
def process(value: Union[int, str]) -> str:
 return str(value)
 
# Callable(函数签名)
from typing import Callable
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
 return func(a, b)
 
# Any —— 跳过类型检查(谨慎使用)
from typing import Any
def flexible(data: Any) -> Any:
 return data

与 C 对比:Python 的类型注解类似于 C 的函数原型声明(int add(int a, int b)),但有以下关键区别:

  • C 的类型是强制性的(不写类型无法编译),Python 的类型注解是可选的
  • C 的类型在运行时不存在(编译后丢弃),Python 的类型注解可通过 __annotations__ 在运行时访问
  • C 是名义类型系统(struct Astruct B 即使结构相同也不同),Python/mypy 支持结构化子类型(Protocol)

2.3 运行 mypy

# 安装
pip install mypy
 
# 检查单个文件
mypy script.py
 
# 检查整个项目
mypy src/
 
# 严格模式(推荐)
mypy --strict src/
 
# 忽略缺少类型注解的第三方库
mypy --ignore-missing-imports src/

2.4 pyproject.toml 配置

[tool.mypy]
python_version = "3.9"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
 
# 忽略缺少 stub 包的第三方库
[[tool.mypy.overrides]]
module = [
 "click.*",
 "yaml.*",
]
ignore_missing_imports = true

2.5 渐进式类型标注

对于已有大型项目,可以渐进式添加类型:

# 仅检查有类型注解的函数
mypy --check-untyped-defs src/
 
# 仅检查特定模块
mypy -m myproject.core
 
# 生成 HTML 报告查看覆盖情况
mypy --html-report ./mypy-report src/

第三节:ruff + mypy 在 CI 中的配置

3.1 Makefile 集成

.PHONY: lint fmt typecheck check
 
lint:
	ruff check src/ tests/
 
fmt:
	ruff format --check src/ tests/
 
typecheck:
	mypy src/
 
# 全面检查
check: lint fmt typecheck

3.2 pre-commit 钩子

# .pre-commit-config.yaml
repos:
 - repo: https://github.com/astral-sh/ruff-pre-commit
 rev: v0.4.0
 hooks:
 - id: ruff
 args: [--fix]
 - id: ruff-format
 
 - repo: https://github.com/pre-commit/mirrors-mypy
 rev: v1.9.0
 hooks:
 - id: mypy
 args: [--strict, --ignore-missing-imports]
 additional_dependencies: [types-PyYAML]
pip install pre-commit
pre-commit install
# 之后每次 git commit 自动运行检查

与 C 对比:pre-commit 钩子类似于 C 项目中的 make lint 在 CI 中自动运行。区别是 pre-commit 在本地提交前就拦截问题,而非等到 CI 流水线。

3.3 VSCode 集成

{
 "python.analysis.typeCheckingMode": "strict",
 "[python]": {
 "editor.defaultFormatter": "charliermarsh.ruff",
 "editor.formatOnSave": true,
 "editor.codeActionsOnSave": {
 "source.fixAll.ruff": "explicit",
 "source.organizeImports.ruff": "explicit"
 }
 }
}

第四节:Python 代码质量工具 vs C 代码质量工具

功能C 语言工具Python 工具
格式化clang-formatruff format
静态分析clang-tidyruff check
编译警告-Wall -Wextraruff check(F/E/W 规则)
类型检查编译器内置mypy
深度检查cppcheckruff(B/SIM 规则)
死代码检测-Wunusedruff(F401/F841)
import 管理#include 顺序检查ruff(I 规则)
现代语法-std=c17ruff(UP 规则)

4.1 配置文件的对应关系

graph LR
 subgraph C["C 项目"]
 CLF[".clang-format"]
 CLT[".clang-tidy"]
 CFL["Makefile CFLAGS"]
 end
 subgraph PY["Python 项目"]
 RUF_FMT["pyproject.toml<br/>[tool.ruff.format]"]
 RUF_LINT["pyproject.toml<br/>[tool.ruff.lint]"]
 RUF_ALL["pyproject.toml<br/>[tool.ruff] + [tool.mypy]"]
 end
 CLF --> RUF_FMT
 CLT --> RUF_LINT
 CFL --> RUF_ALL

核心差异:C 语言的类型检查是编译器内置的、强制性的——int x = "hello" 无法编译。Python 的类型检查是可选的、工具辅助的——只有运行 mypy 才会发现 x: int = "hello"。这意味着 Python 项目的代码质量更多依赖于工程纪律和 CI 集成。

4.2 质量工具对比示例

// C 语言:编译器发现问题
#include <stdio.h>
 
int add(int a, int b) { return a + b; }
 
int main() {
 int unused = 42;
 int result = add("hello", 5); // 编译错误!
 // warning: passing argument 1 of 'add' makes integer from pointer
}
# Python:如果不运行 ruff + mypy,这些错误会静默通过
def add(a: int, b: int) -> int:
 return a + b
 
unused = 42 # ruff: F841 -- 没问题,不报错
 
result = add("hello", 5) # mypy: error: Argument 1 has incompatible type "str"
 # ruff: 不检查类型
# C 语言的完整质量检查流程
clang-format --dry-run src/main.c # 格式检查
clang-tidy src/main.c -- -Iinclude # 静态分析
gcc -Wall -Wextra -Werror -c src/main.c # 编译 + 警告即错误
cppcheck --enable=all src/ # 深度检查
 
# Python 的完整质量检查流程
ruff format --check src/ # 格式检查
ruff check src/ # lint 检查
mypy --strict src/ # 类型检查

练习

以下题目用于验证本章所学内容:

题号题目链接涉及知识点
本章无对应力扣题请用动手练习题自检