如何用Python实现代码预处理器并解决AST递归遍历代码节点的问题
如何用Python实现代码预处理器并解决AST递归遍历代码节点的问题
看起来你已经找对方向了——用AST做代码分析确实是处理这类问题的最优解,不过你遇到的递归遍历问题,大概率是没搞懂NodeVisitor的核心工作逻辑。
简单说,你提到的If、函数定义、For循环这些嵌套节点没被遍历到,几乎肯定是因为你在自定义的generic_visit方法里,没有正确调用父类的generic_visit来触发子节点的递归遍历。默认情况下,NodeVisitor的generic_visit会自动遍历当前节点的所有子属性,但如果你重写这个方法时只处理了当前节点,却忘了调用父类方法,那嵌套在里面的代码就会被忽略。
下面给你一个修正后的完整示例,不仅能解决递归遍历问题,还能帮你提取函数名、调用、参数这些你需要的信息:
import ast class MyVisitor(ast.NodeVisitor): def __init__(self): self.processed_nodes = [] # 用来存所有处理后的节点信息 def generic_visit(self, node): # 先记录当前节点的基础信息(可以根据你的需求调整字段) basic_info = { "node_type": type(node).__name__, "line_number": node.lineno, "column": node.col_offset } self.processed_nodes.append(basic_info) # 关键!必须调用父类的generic_visit,才能递归遍历子节点 super().generic_visit(node) # 专门处理函数定义节点,提取函数名和参数 def visit_FunctionDef(self, node): func_detail = { "node_type": "FunctionDefinition", "function_name": node.name, "parameters": [arg.arg for arg in node.args.args], "start_line": node.lineno } self.processed_nodes.append(func_detail) # 别忘了递归处理函数体里的所有语句 self.generic_visit(node) # 处理函数调用节点,提取调用的函数名和参数 def visit_Call(self, node): # 处理函数名的情况:如果是直接调用变量名,就取id;否则用dump看结构 func_name = node.func.id if isinstance(node.func, ast.Name) else ast.dump(node.func) call_detail = { "node_type": "FunctionCall", "function_name": func_name, "arguments": [ast.dump(arg) for arg in node.args], "line_number": node.lineno } self.processed_nodes.append(call_detail) # 递归处理参数里的子节点(比如参数是表达式的情况) self.generic_visit(node) # 处理赋值语句,提取赋值目标和值 def visit_Assign(self, node): assign_detail = { "node_type": "Assignment", "targets": [ast.dump(target) for target in node.targets], "value": ast.dump(node.value), "line_number": node.lineno } self.processed_nodes.append(assign_detail) self.generic_visit(node) # 测试用的示例代码 test_code = """ def UART(ONE): ONE = UUUU001 + 4 print(ONE) if ONE > 5: for i in range(3): print(i) """ # 把代码解析成AST语法树 code_ast = ast.parse(test_code) # 创建Visitor实例并遍历AST visitor = MyVisitor() visitor.visit(code_ast) # 打印处理后的结果 for item in visitor.processed_nodes: print(item)
再给你几个实用的小技巧:
- 如果你不确定某个节点的结构,可以先用
print(ast.dump(code_ast, indent=2))把整个AST树打印出来,直观看到各个节点的属性和嵌套关系。 - 如果你只需要处理特定类型的节点,不用写全所有
visit_XXX方法,父类的generic_visit会自动跳过未自定义处理的节点,但依然会递归遍历它们的子节点。 - 要是你之后需要修改代码(比如自动补全、重构),可以用
ast.NodeTransformer代替NodeVisitor,它支持修改或替换节点。
备注:内容来源于stack exchange,提问作者Coliban




