如何找出Python中调用指定函数的所有自定义函数(含深层调用)
如何用Python AST检测函数直接/间接调用目标函数(含跨文件场景)
要解决间接调用(包括跨文件)的检测问题,你需要构建函数调用依赖图,并递归追踪每个函数的调用链,直到找到目标函数(比如print)。下面是具体实现方案:
核心思路
- 解析单个文件的AST,记录每个函数内部调用的所有函数(包括本地函数和导入的外部函数)
- 对导入的外部模块,递归解析其AST,获取对应函数的调用关系
- 对每个函数,递归检查其调用链中是否存在目标函数(如
print)
完整实现代码
import ast import os from typing import Dict, Set, List # 存储所有已解析模块的函数调用关系:{模块路径: {函数名: 调用的函数集合}} module_call_graph: Dict[str, Dict[str, Set[str]]] = {} def get_module_path(import_name: str, current_file: str) -> str: """将导入的模块名转换为对应的文件路径""" current_dir = os.path.dirname(os.path.abspath(current_file)) module_path = os.path.join(current_dir, f"{import_name.replace('.', '/')}.py") return module_path def parse_file_call_graph(file_path: str) -> Dict[str, Set[str]]: """解析单个文件,生成函数调用关系:{函数名: 调用的函数集合}""" if file_path in module_call_graph: return module_call_graph[file_path] with open(file_path, "r", encoding="utf-8") as f: tree = ast.parse(f.read()) call_graph = {} current_func = None imports = {} # 存储导入映射:{本地名称: (模块名, 原函数名)} class CallVisitor(ast.NodeVisitor): nonlocal current_func, imports def visit_FunctionDef(self, node: ast.FunctionDef): nonlocal current_func prev_func = current_func current_func = node.name call_graph[current_func] = set() self.generic_visit(node) current_func = prev_func def visit_Call(self, node: ast.Call): if current_func is None: self.generic_visit(node) return # 处理直接调用的本地函数或内置函数 if isinstance(node.func, ast.Name): call_graph[current_func].add(node.func.id) # 处理导入的函数调用(如some_function()) elif isinstance(node.func, ast.Name) and node.func.id in imports: mod_name, func_name = imports[node.func.id] mod_path = get_module_path(mod_name, file_path) # 记录为"模块名.函数名"的形式,避免冲突 call_graph[current_func].add(f"{mod_name}.{func_name}") self.generic_visit(node) def visit_ImportFrom(self, node: ast.ImportFrom): for alias in node.names: imports[alias.asname or alias.name] = (node.module, alias.name) self.generic_visit(node) CallVisitor().visit(tree) module_call_graph[file_path] = call_graph return call_graph def has_target_call(func_name: str, target_func: str, file_path: str, visited: Set[str] = None) -> bool: """递归检查函数是否直接或间接调用目标函数""" if visited is None: visited = set() # 避免循环调用导致无限递归 if func_name in visited: return False visited.add(func_name) call_graph = parse_file_call_graph(file_path) # 如果是内置函数,直接判断 if func_name == target_func: return True # 处理跨模块的函数(格式为"模块名.函数名") if "." in func_name: mod_name, external_func = func_name.split(".", 1) mod_path = get_module_path(mod_name, file_path) return has_target_call(external_func, target_func, mod_path, visited.copy()) # 检查当前函数的直接调用 if func_name not in call_graph: return False for called_func in call_graph[func_name]: if called_func == target_func or has_target_call(called_func, target_func, file_path, visited.copy()): return True return False if __name__ == "__main__": target_file = "file_two.py" target_func = "print" # 先解析目标文件的函数列表 with open(target_file, "r", encoding="utf-8") as f: tree = ast.parse(f.read()) # 遍历所有自定义函数,检查是否调用print for node in ast.walk(tree): if isinstance(node, ast.FunctionDef): func_name = node.name if has_target_call(func_name, target_func, target_file): print(f"函数 {func_name} 直接或间接调用了 {target_func}")
代码说明
parse_file_call_graph:解析单个文件的AST,生成函数调用关系,同时处理from ... import ...的导入映射,将导入的函数关联到对应模块。has_target_call:递归追踪函数调用链,处理跨模块函数的解析,同时通过visited集合避免循环调用的无限递归。- 主程序:遍历目标文件的所有自定义函数,逐个检查是否调用了目标函数(
print)。
测试结果
运行上述代码,针对你的示例文件,会输出:
函数 one 直接或间接调用了 print 函数 two 直接或间接调用了 print 函数 three 直接或间接调用了 print 函数 four 直接或间接调用了 print
内容的提问来源于stack exchange,提问作者houseofleft
相关产品推荐
相关产品推荐

