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

如何借助Jedi库生成Python函数的完整调用链?

如何用Jedi构建Python函数的完整调用链

核心结论

Jedi本身不直接支持递归获取完整调用链,但可以通过手动递归遍历调用者的方式实现这个功能。Jedi的Name对象提供了definitions和references方法,结合递归向上查找,就能构建多层调用链。

实现思路

要生成从顶层函数到目标函数的完整调用链,需按以下步骤操作:

  1. 精准定位目标函数的定义位置(比如models.user.User.get_by_id的定义)
  2. 查找所有直接调用该函数的位置(即第一层调用者,如services.products.get_products_by_user_id)
  3. 对每个调用者,递归查找调用它的上层函数,直到没有更上层的调用者(顶层函数,如controllers.products.ProductList.get)
  4. 整理递归结果,输出树形结构的调用链

修改后的脚本实现

基于你的现有脚本,以下是实现完整调用链的版本:

import os
import jedi

proj_base_path = '/path/to/the/project_folder'
target_fn_name = 'get_by_id'
target_module = 'models.user'  # 明确目标函数所在模块,避免同名函数混淆

def get_file_paths(path):
    ex_path = os.path.expanduser(path)
    for root, dirs, files in os.walk(ex_path):
        for f_path in files:
            if os.path.splitext(f_path)[1] != '.py':
                continue
            yield os.path.join(root, f_path)

def get_full_name(name):
    """生成函数/方法的完整限定名,如 controllers.products.ProductList.get"""
    if name.type in ('function', 'method'):
        # 处理类方法:模块名.类名.方法名
        if name.parent() and name.parent().type == 'class':
            return f"{name.module_name}.{name.parent().name}.{name.name}"
        # 处理普通函数:模块名.函数名
        return f"{name.module_name}.{name.name}"
    return name.name

def find_callers(name, visited=None):
    """递归查找调用当前函数的所有上层函数"""
    if visited is None:
        visited = set()
    callers = []
    # 获取当前函数的所有调用引用
    references = name.references()
    for ref in references:
        # 跳过自身定义和重复节点
        if ref in visited or ref.type not in ('function', 'method'):
            continue
        visited.add(ref)
        # 向上定位到调用当前函数的上层函数/方法
        caller = ref.parent()
        while caller and caller.type not in ('function', 'method', 'class'):
            caller = caller.parent()
        if caller and caller not in visited:
            callers.append(caller)
            # 递归查找调用者的上层函数
            callers.extend(find_callers(caller, visited))
    # 去重后返回
    return list({c for c in callers})

def build_call_chain(target_name):
    """构建从顶层到目标函数的完整调用链"""
    call_chain = []
    all_callers = find_callers(target_name)
    
    def traverse(caller, current_chain):
        new_chain = current_chain + [caller]
        # 查找调用当前caller的上层函数
        upper_callers = [c for c in all_callers if caller in find_callers(c, set())]
        if not upper_callers:
            # 已到顶层函数,添加完整链(顶层->...->目标)
            call_chain.append(new_chain + [target_name])
        else:
            for upper in upper_callers:
                traverse(upper, new_chain)
    
    # 先处理直接调用目标函数的节点
    direct_callers = [c for c in all_callers if target_name in find_callers(c, set())]
    for caller in direct_callers:
        traverse(caller, [])
    return call_chain

def print_call_chain(chain):
    """打印树形结构的调用链"""
    for i, node in enumerate(chain):
        indent = '       ' * i
        if i == 0:
            print(f"* {get_full_name(node)}")
        else:
            print(f"{indent}|")
            print(f"{indent} `---> * {get_full_name(node)}")
    print()

if __name__ == '__main__':
    # 第一步:定位目标函数的定义
    target_def = None
    for file_path in get_file_paths(proj_base_path):
        script = jedi.Script(path=file_path, project=jedi.Project(proj_base_path))
        names = script.search(target_fn_name)
        for name in names:
            # 匹配目标函数的模块和类型
            if name.module_name == target_module and name.type == 'method':
                target_def = name
                break
        if target_def:
            break

    if not target_def:
        print(f"未找到目标函数 {target_module}.{target_fn_name}")
        exit()

    # 第二步:构建并打印所有调用链
    chains = build_call_chain(target_def)
    print(f"找到 {len(chains)} 条调用链:")
    for idx, chain in enumerate(chains, 1):
        print(f"调用链 {idx}:")
        print_call_chain(chain)

关键说明

  • get_full_name:将Jedi的Name对象转换为易读的完整限定名,方便输出展示。
  • find_callers:通过references()获取函数的所有调用位置,再用parent()定位到调用所在的上层函数,递归遍历实现深层调用查找。
  • build_call_chain:整理调用关系,从直接调用者向上追溯到顶层函数,构建完整的调用路径。
  • 防循环处理:用visited集合记录已处理的节点,避免递归陷入死循环。

注意事项

  1. Jedi的静态分析存在局限性,无法识别动态生成的调用(如getattr调用、动态拼接的函数名)。
  2. 必须明确目标函数的模块和类型,避免同名函数干扰查找结果。
  3. 大型项目中递归遍历可能较慢,可通过限制扫描目录(如只扫描controllers、services)优化性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 20:10:43