僵尸模块指被导入但未实际使用的模块,需通过ast静态分析构建依赖图、追踪符号引用,并处理动态导入、类型注解、副作用等边界情况。

通过 import 语句的静态分析识别“僵尸模块”(即被导入但从未实际使用的模块),核心在于:构建完整的模块依赖图,追踪每个 import 的符号是否在当前作用域中被引用。这不是运行时检测,而是基于 AST 的代码结构推断——准确率高、无需执行,但需谨慎处理动态导入、类型注解、副作用等边界情况。
提取所有 import 声明并归类
遍历项目全部 .py 文件,用 ast.parse() 解析为抽象语法树,提取四类 import 节点:
- import A → 记录模块名 A 及其别名(如 import numpy as np 中记 np → numpy)
- from A import B, C as D → 记录导入的符号 B, D,映射到 A.B, A.C
- from A import * → 标记为“模糊导入”,暂不判定使用状态(需结合 __all__ 或保守排除)
- from . import X 或 from ..utils import Y → 解析相对路径,转换为绝对模块名以便统一比对
构建符号引用图并标记活跃性
在同一文件内,再次遍历 AST,收集所有 Name 节点(变量读取)、Attribute 节点(如 os.path.join)、函数调用、类实例化等实际使用行为:
- 若出现 np.array(...),则标记 np 为“已使用”;若只有 import numpy as np 且无任何 np.xxx,则 np 待定为僵尸
- 注意别名链:如 import pandas as pd; df = pd.DataFrame() → pd 活跃 → pandas 活跃
- 忽略出现在 type: "xxx" 注解、字符串、注释中的名称(需用 ast.get_source_segment() 辅助判断上下文)
识别并过滤常见误报场景
静态分析易将有副作用的导入判为“未使用”,需人工规则兜底:
- 仅触发模块级副作用的导入:如 import django.setup、import setproctitle,虽无显式调用,但必须保留 → 在配置中维护“副作用模块白名单”
- 运行时动态使用:如 getattr(module, name)()、importlib.import_module(x) → 这类 import 应避免写成静态形式,或加 # noqa: F401 注释跳过检测
- 条件导入或测试专用模块:如 if DEBUG: import pdb → 需结合控制流分析,或允许按目录/文件名豁免(如 tests/ 下的 pytest 导入)
生成报告并安全剔除
汇总所有“声明但未使用”的 import 行信息(文件路径、行号、原始语句),输出为结构化报告(JSON/CSV),支持分级处理:
- 自动删除:对确定无副作用、非条件、非类型导入的单行 import(如 import logging 且全文无 logging.xxx)可直接用 libcst 或正则替换移除
- 人工复核队列:含相对导入、* 导入、疑似副作用的条目,输出为 Markdown 表格,标注风险等级
- CI 集成建议:在 pre-commit 或 PR 检查中运行扫描,阻断新增僵尸 import,但不对历史存量强制清理,避免意外破坏
不复杂但容易忽略的是 import 的“可见性范围”——一个模块在 __init__.py 中 re-export 的符号,可能被下游间接使用。因此完整方案需递归分析包内所有 __init__.py 的 __all__ 和 public 导入,才能真正闭环。










