返回专题首页

Python 专题

玩转 Python AST:静态分析、代码生成与批量改造

对很多人来说,AST 似乎属于编译器或工具链的领域,离日常业务开发很远。但只要你需要做静态分析、代码检查、批量改造甚至简单的代码生成,AST 就会立刻变得非常实用。

Python 专题第 37 篇 / 39 篇5 分钟

对很多人来说,AST 似乎属于编译器或工具链的领域,离日常业务开发很远。但只要你需要做静态分析、代码检查、批量改造甚至简单的代码生成,AST 就会立刻变得非常实用。

这一节我们会从 Python 标准库里的 ast 模块出发,看看源码为什么可以被解析成结构化数据,又如何在这个基础上实现分析、变换与生成。理解这一层,也会让你对语言本身有一种新的视角。

前置知识:代码为什么可以被“当作数据”处理?

我们平时把代码当作“让程序运行的文本”,但从工具链视角看,它首先是一种有语法规则的结构化语言。

只要能把源代码解析成语法树,程序就可以不再只是执行代码,还能:

  • 分析代码;
  • 统计代码;
  • 修改代码;
  • 重新生成代码。

所以 AST 的意义,不在于炫技,而在于让代码第一次以“数据结构”的形式出现在你面前。

Python ast 模块的基本使用

Python 标准库已经提供了 ast 模块。它可以把一段源码解析成语法树节点。

你不一定要一开始就记住所有节点类型,更重要的是先接受一个新心智:一段 def、一个 if、一个函数调用,在 AST 里都有明确结构,而不是只是一串文本。

这意味着你做代码分析时,不需要靠字符串查找硬猜逻辑,而可以基于真实语法结构来判断。

例如:

import ast

source = """
def hello(name):
    print(name)
"""

tree = ast.parse(source)
print(ast.dump(tree, indent=2))

第一次看到树状结果时,你会明显感受到:代码已经不再只是文本,而是被拆成了可遍历、可分析的节点结构。

读取、遍历与分析语法树

拿到语法树之后,最常见的动作通常是遍历。

遍历的意义在于,你可以系统地找到:

  • 所有函数定义;
  • 所有导入语句;
  • 某类调用表达式;
  • 某类写法是否出现。

所以 AST 在静态检查里的价值非常直接。你不再只能问“某个文本有没有出现”,而是可以问“某种语法结构有没有出现”。

下面这个例子,就是最小的函数收集器:

import ast
from pathlib import Path


class FunctionCollector(ast.NodeVisitor):
    def __init__(self):
        self.names = []

    def visit_FunctionDef(self, node):
        self.names.append(node.name)
        self.generic_visit(node)


tree = ast.parse(Path("demo.py").read_text(encoding="utf-8"))
collector = FunctionCollector()
collector.visit(tree)
print(collector.names)

这时你统计的已经不是“文本里出现了 def”,而是真正的函数定义节点。

修改 AST 并生成新代码

AST 更进一步的能力,是变换。

也就是说,你不仅能看懂代码结构,还可以:

  • 定位旧写法;
  • 替换成新写法;
  • 再输出成新代码。

这使得批量改造成为可能。很多过去靠手工搜改、风险极高的工作,只要规则足够明确,就能被工具化。

当然也要注意,代码生成和改写的难点从来不只是“改出来”,而是改完之后是否还保持语义正确、可读性稳定。

如果要做最小改写,可以先从 NodeTransformer 开始:

import ast


class ReplaceFoo(ast.NodeTransformer):
    def visit_Name(self, node):
        if node.id == "foo":
            return ast.copy_location(ast.Name(id="bar", ctx=node.ctx), node)
        return node


tree = ast.parse("print(foo)")
new_tree = ReplaceFoo().visit(tree)
ast.fix_missing_locations(new_tree)
print(ast.unparse(new_tree))

输出会变成:

print(bar)

这就是批量改造工具最小可行原型的雏形。

实战场景:静态检查、批量改造与代码生成

示例:函数收集

一个非常典型的小工具,就是统计某个项目里定义了哪些函数、分布在哪些文件。这类需求如果靠字符串查找容易误判,但 AST 可以更准确识别真正的函数定义节点。

示例:自动替换旧写法

另一个很实用的场景,是批量迁移旧 API 调用。只要你能明确识别旧调用模式,并知道新目标结构,就有机会通过 AST 做更安全的自动替换。

所以 AST 的价值并不只在“编译器相关”,它其实非常适合进入工程工具链、代码治理和批量重构场景。

真正开始动手之后,你会发现 AST 最适合做的是“规则明确、结构稳定、可批量执行”的修改,而不是替代你做所有复杂重构决策。

总结

这一节我们用更底层的视角重新看待了 Python 代码本身,理解了 AST 不只是编译器世界里的概念,也可以成为静态检查、代码迁移和工具开发的基础。换一个层次理解语言,往往会带来完全不同的能力边界。

下一节我们会把整套专题中的高频知识点重新收束回面试场景,看看哪些问题最值得重点准备,又该如何回答得更有层次。

扩展阅读

如果你对这部分内容产生了兴趣,后续很适合继续沿着这些方向深入:

  • 看看 Ruff、Black、mypy 这类工具背后分别在哪一层做分析;
  • 进一步了解 CST、格式保留改写与 CodeMod 工具链;
  • 把一个小型批量改造脚本真正落地到自己的项目里练一次。