Python 类型检查器
对 Python 代码库进行深入分析,检查类型注释的覆盖率和质量。
发现未标注类型的函数、不正确的注释、缺失的泛型、不正确的 Protocol 使用以及破坏类型检查器推断的模式。
使用场景:审计类型安全、准备启用严格的 mypy、审查注释质量或从未标注类型的 Python 迁移到标注类型的 Python。
分析步骤
cat pyproject.toml 2>/dev/null | head -30
python3 --version 2>/dev/null
# Mypy 配置
cat pyproject.toml 2>/dev/null | grep -A30 '\[tool.mypy\]'
cat mypy.ini 2>/dev/null || cat setup.cfg 2>/dev/null | grep -A20 '\[mypy\]'
# py.typed 标记(PEP 561)
find . -name "py.typed" -not -path '
/venv/' 2>/dev/null
find . -name "
.pyi" -not -path '/venv/
' 2>/dev/null | head -10
find . -name ".py" -not -path '
/venv/' -not -path '
/__pycache__/' | wc -l
确定:Python 版本、类型检查器配置和严格性、是否包含类型(py.typed)、项目大小。
# 没有返回类型注释的函数
grep -rn 'def [a-zA-Z_]\+(' --include="
.py" . 2>/dev/null \
| grep -v 'venv/\|__pycache__\|_test\.py\|test_' | grep -v '\-> ' | head -30
# 完全未标注类型的文件
for f in $(find . -name ".py" -not -path '
/venv/' -not -path '
/__pycache__/' \
-not -name '
_test.py' 2>/dev/null); do
if ! grep -q ':\s[A-Z]\|-> \|: str\|: int\|: float\|: bool\|: list\|: dict' "$f" 2>/dev/null;
then echo "UNTYPED: $f"
fi
done | head -15
# 覆盖率估计
total=$(grep -rc 'def ' --include="
.py" . 2>/dev/null | grep -v 'venv/' | awk -F: '{s+=$2} END {print s}')
typed=$(grep -rc '\-> ' --include=".py" . 2>/dev/null | grep -v 'venv/' | awk -F: '{s+=$2} END {print s}')
echo "Coverage: $typed / $total functions have return type annotations"
# 已弃用的 typing 导入(在 3.9+ 中使用内置类型)
grep -rn 'from typing import List\|from typing import Dict\|from typing import Tuple\|from typing import Set' \
--include="
.py" . 2>/dev/null | grep -v 'venv/' | head -15
# from __future__ import annotations(PEP 563)
grep -rn 'from __future__ import annotations' --include=".py" . 2>/dev/null | grep -v 'venv/' | head -10
# TYPE_CHECKING 守卫
grep -rn 'TYPE_CHECKING' --include="
.py" . 2>/dev/null | grep -v 'venv/' | head -10
标记:已弃用的 typing 导入:typing.List/Dict/Tuple/Set 在 3.9+ 中已弃用 —— 使用 list/dict/tuple/set
缺失 TYPE_CHECKING 守卫:仅用于注释的导入应放在 if TYPE_CHECKING: 下以避免运行时开销和循环导入
不一致的风格:在同一代码库中混合使用 List[int] 和 list[int]- Any、Union 和 Escape Hatches
# Any 使用(排除导入)
grep -rn '\bAny\b' --include=".py" . 2>/dev/null | grep -v 'venv/\|_test\.py\|from typing' | head -20
# type: ignore 注释
grep -rn 'type:\s
ignore' --include=".py" . 2>/dev/null | grep -v 'venv/' | head -15
# cast() 使用
grep -rn 'cast(' --include="
.py" . 2>/dev/null | grep -v 'venv/' | head -10
# Optional 无 None 检查模式
grep -rn 'Optional\[' --include=".py" . 2>/dev/null | grep -v 'venv/' | head -10
标记:过度使用 Any:每个 Any 都是类型安全的漏洞 —— 每个使用都应有理由
返回 Any 的函数:调用者失去所有类型信息
type: ignore 无错误代码:应指定类似 # type: ignore[assignment] 以保持可维护性
cast() 过度使用:运行时无操作的函数,欺骗类型检查器 —— 更喜欢 isinstance 收缩
Optional 无 None 处理:注释 Optional[X] 但从不检查 None
# TypeVar 定义
grep -rn 'TypeVar(' --include="
.py" . 2>/dev/null | grep -v 'venv/' | head -10
# ParamSpec(装饰器 typing,3.10+)
grep -rn 'ParamSpec' --include=".py" . 2>/dev/null | grep -v 'venv/' | head -5
# Protocol 定义
grep -rn 'class.
Protocol' --include=".py" . 2>/dev/null | grep -v 'venv/\|from typing' | head -10
# ABC 与 Protocol
grep -rn '@abstractmethod\|class.
ABC' --include=".py" . 2>/dev/null | grep -v 'venv/' | head -10
# 复杂的 Callable 类型
grep -rn 'Callable\[' --include="
.py" . 2>/dev/null | grep -v 'venv/' | head -10
# dict[str, Any](可能是 TypedDict)
grep -rn 'dict\[str,\sAny\]\|Dict\[str,\s
Any\]' --include=".py" . 2>/dev/null | grep -v 'venv/' | head -10
标记:无约束的 TypeVar:T = TypeVar('T') 接受任何类型 —— 如果函数需要特定能力,应添加 bound=
缺失 ParamSpec 的装饰器:没有 ParamSpec 的装饰器会失去被装饰函数的签名
ABC 可以使用 Protocol:如果基类没有共享实现,Protocol 提供了无继承的结构子类型
复杂的 Callable 类型:Callable[[str, int, Optional[dict]], Awaitable[list[str]]] —— 使用带有 __call__ 的 Protocol
dict[str, Any] 用于结构化数据:使用 TypedDict 进行类型安全的键访问
# Dataclass 问题
grep -A20 '@dataclass' --include="*.py" . 2>/dev/null | grep '^\s\+[a-z_]\+ =' | grep -v