如何通过Python AST判断特定函数是否被调用(不受函数名称影响)
问题:用Python AST判断特定函数是否被调用(不受函数名称影响)
在运行时可以通过hash()判断函数实例,但用AST做静态分析时,仅检查调用节点的函数名(比如node.func.id == "identity")无法覆盖函数被别名、重命名导入的情况,比如:
x = identity x(1) # 无法被原Visitor识别 from mymod import identity as id_func id_func(2) # 也无法识别
解决方案:通过符号追踪绑定关系
AST静态分析的核心是追踪每个名字对应的原始函数定义,需要维护一个符号表,记录变量/别名与目标函数的绑定关系,步骤如下:
- 先定位目标函数的AST定义节点(比如
ast.FunctionDef类型,名字为目标函数名的节点) - 遍历AST时,维护符号表:
- 处理函数定义:记录函数名与对应
FunctionDef节点的映射 - 处理赋值语句:如果赋值的是已绑定目标函数的名字,将新变量也绑定到目标函数
- 处理导入语句:如果导入的是目标函数,记录别名与目标函数的映射
- 处理函数定义:记录函数名与对应
- 访问Call节点时,解析调用的函数对应的符号,判断是否指向目标函数的定义节点
示例代码实现
import ast class FunctionCallTracker(ast.NodeVisitor): def __init__(self, target_func_name: str): self.target_func_name = target_func_name self.target_func_def = None # 存储目标函数的FunctionDef节点 self.symbol_table = {} # 记录名字到函数定义的映射 self.target_calls = [] # 存储找到的目标函数调用节点 def visit_FunctionDef(self, node: ast.FunctionDef): # 先找到目标函数的定义节点 if node.name == self.target_func_name: self.target_func_def = node self.symbol_table[node.name] = node # 递归处理函数内部的节点 self.generic_visit(node) def visit_Assign(self, node: ast.Assign): # 处理赋值语句,比如x = identity for target in node.targets: if isinstance(target, ast.Name): # 解析赋值的右侧表达式,看是否指向目标函数 value_node = node.value if isinstance(value_node, ast.Name): if value_node.id in self.symbol_table: self.symbol_table[target.id] = self.symbol_table[value_node.id] self.generic_visit(node) def visit_ImportFrom(self, node: ast.ImportFrom): # 处理from ... import ... as ...的情况 for alias in node.names: if alias.name == self.target_func_name: # 记录别名到目标函数的映射 self.symbol_table[alias.asname or alias.name] = self.target_func_def self.generic_visit(node) def visit_Call(self, node: ast.Call): # 解析调用的函数是否指向目标函数 called_func = self._resolve_call_target(node.func) if called_func is self.target_func_def: self.target_calls.append(node) print(f"找到目标函数调用,位置: {node.lineno}行") self.generic_visit(node) def _resolve_call_target(self, func_node: ast.AST): # 递归解析调用的目标,处理简单的名字引用 if isinstance(func_node, ast.Name): return self.symbol_table.get(func_node.id) # 可扩展处理属性访问(比如obj.identity)、嵌套调用等场景 return None # 测试代码 test_code = """ def identity(x): return x x = identity x(10) from mymod import identity as id_func id_func(20) def wrapper(func): func(30) wrapper(identity) """ tree = ast.parse(test_code) tracker = FunctionCallTracker("identity") tracker.visit(tree) print(f"总共找到{len(tracker.target_calls)}次目标函数调用")
说明
- 这个实现能处理直接赋值别名、导入别名、函数作为参数传递后调用的常见场景
- 如需支持更复杂的情况(比如链式赋值、模块属性调用),需要进一步扩展符号解析逻辑
- 静态分析存在局限性,无法处理运行时动态绑定的函数(比如
eval("identity")),这类场景只能靠运行时检测
内容的提问来源于stack exchange,提问作者Harry Lees
相关产品推荐
相关产品推荐

