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

如何改进ast+NetworkX调用图生成算法以支持嵌套函数?

解决嵌套函数调用图生成的核心优化方案

要解决嵌套函数场景下的错误调用边问题,核心是跟踪每个调用节点所属的函数上下文,避免扁平化遍历导致的调用者归属错误。以下是具体实现思路和代码:

核心思路

  1. 维护函数上下文栈:遍历AST时,进入函数定义(ast.FunctionDef)时记录当前函数,离开时恢复上一层函数,确保每个调用节点(ast.Call)能关联到正确的最内层调用者。
  2. 用递归遍历替代ast.walk:ast.walk会扁平化遍历所有节点,丢失上下文层级,改用ast.NodeVisitor的递归遍历,保证节点在所属函数的上下文内被处理。
  3. 嵌套函数唯一标识:用父函数名.子函数名的格式标记嵌套函数,避免同名函数的冲突,同时准确识别调用目标。

完整实现代码

import ast
import networkx as nx

class CallGraphBuilder(ast.NodeVisitor):
    def __init__(self):
        self.graph = nx.DiGraph()
        self.current_func = None  # 跟踪当前所在的最内层函数
        self.all_funcs = {}       # 存储所有函数:键为唯一标识,值为AST节点

    def visit_FunctionDef(self, node):
        # 生成函数唯一标识(嵌套函数带父级前缀)
        func_id = node.name
        if self.current_func:
            func_id = f"{self.current_func}.{func_id}"
        
        self.all_funcs[func_id] = node
        self.graph.add_node(func_id)

        # 切换上下文:进入当前函数
        prev_func = self.current_func
        self.current_func = func_id

        # 遍历函数体所有节点
        self.generic_visit(node)

        # 恢复上下文:退出当前函数
        self.current_func = prev_func

    def visit_Call(self, node):
        # 只处理直接调用函数名的情况(忽略obj.func()这类属性调用)
        if isinstance(node.func, ast.Name):
            called_name = node.func.id
            matched_func = None

            # 优先匹配当前上下文的嵌套函数
            if self.current_func:
                qualified_name = f"{self.current_func}.{called_name}"
                if qualified_name in self.all_funcs:
                    matched_func = qualified_name
            # 再匹配全局函数
            if not matched_func and called_name in self.all_funcs:
                matched_func = called_name

            # 确认调用者和被调用者都在当前文件内,添加调用边
            if matched_func and self.current_func:
                self.graph.add_edge(self.current_func, matched_func)
        
        # 继续遍历调用节点的子节点(比如参数中的嵌套调用)
        self.generic_visit(node)

# 生成调用图的入口函数
def build_call_graph(source_code):
    ast_tree = ast.parse(source_code)
    builder = CallGraphBuilder()
    builder.visit(ast_tree)
    return builder.graph

# 测试示例
test_source = """
def f():
    def g():
        h()
        def k():
            g()
        k()
    g()
    h()

def h():
    pass
"""

call_graph = build_call_graph(test_source)

# 打印调用边
print("生成的调用边:")
for caller, callee in call_graph.edges():
    print(f"{caller} -> {callee}")

代码效果说明

运行上述测试代码,会输出正确的调用边:

生成的调用边:
f -> g
f -> h
g -> h
g -> k
k -> g

完全符合嵌套函数的调用逻辑,不会出现f->k这类错误边。

额外优化建议

  • 若需支持异步函数,可添加visit_AsyncFunctionDef方法,逻辑与visit_FunctionDef一致。
  • 若要处理函数别名(如x = g; x()),可扩展visit_Assign方法跟踪变量与函数的映射关系。
  • 可忽略空函数或无调用的函数,减少图中冗余节点。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 10:24:10