You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何找出Python中调用指定函数的所有自定义函数(含深层调用)

如何用Python AST检测函数直接/间接调用目标函数(含跨文件场景)

要解决间接调用(包括跨文件)的检测问题,你需要构建函数调用依赖图,并递归追踪每个函数的调用链,直到找到目标函数(比如print)。下面是具体实现方案:

核心思路

  1. 解析单个文件的AST,记录每个函数内部调用的所有函数(包括本地函数和导入的外部函数)
  2. 对导入的外部模块,递归解析其AST,获取对应函数的调用关系
  3. 对每个函数,递归检查其调用链中是否存在目标函数(如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}")

代码说明

  1. parse_file_call_graph:解析单个文件的AST,生成函数调用关系,同时处理from ... import ...的导入映射,将导入的函数关联到对应模块。
  2. has_target_call:递归追踪函数调用链,处理跨模块函数的解析,同时通过visited集合避免循环调用的无限递归。
  3. 主程序:遍历目标文件的所有自定义函数,逐个检查是否调用了目标函数(print)。

测试结果

运行上述代码,针对你的示例文件,会输出:

函数 one 直接或间接调用了 print
函数 two 直接或间接调用了 print
函数 three 直接或间接调用了 print
函数 four 直接或间接调用了 print

内容的提问来源于stack exchange,提问作者houseofleft

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 15:57:03