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

如何获取Python函数调用中被调用函数的AST及作用域变量值?

获取被调用函数的AST及作用域变量值的解决方案

我来帮你解决这两个问题——分析函数AST时无法获取被调用函数的AST,以及如何捕获作用域内的变量值,下面是具体的实现思路和代码示例:

一、获取被调用函数的AST

你当前能拿到f2的AST,但无法获取它调用的f1的AST,核心原因是inspect.getsource(f)只能获取目标函数本身的源码,不会自动关联它调用的其他函数。要解决这个问题,需要从f2的Call节点中提取被调用函数的名称,再定位到该函数的定义并解析其AST。

具体实现步骤:

  1. 遍历目标函数AST,提取被调用函数名
    用AST遍历器找到f2代码中的Call节点,提取出被调用函数的标识符(比如f1)。
  2. 定位被调用函数的对象
    根据函数名,从当前模块或对应模块中获取函数对象,可通过globals()、inspect.getmodule()或函数的__module__属性实现。
  3. 获取被调用函数源码并解析为AST
    用inspect.getsource()拿到函数源码,再用ast.parse()解析成AST结构。

代码示例(修改other.py):

import inspect
import ast
from ast import NodeVisitor

class CallVisitor(NodeVisitor):
    def __init__(self):
        self.called_funcs = []
    
    def visit_Call(self, node):
        # 仅处理直接的函数名调用(如f1(),暂不处理obj.f()这类属性调用)
        if isinstance(node.func, ast.Name):
            self.called_funcs.append(node.func.id)
        self.generic_visit(node)

def check(f):
    # 解析目标函数f的AST
    f_source = inspect.getsource(f)
    f_ast = ast.parse(f_source)
    
    # 遍历f的AST,收集所有被调用的函数名
    visitor = CallVisitor()
    visitor.visit(f_ast)
    
    # 获取f所在的模块对象
    f_module = inspect.getmodule(f)
    
    # 逐个获取被调用函数的AST
    for func_name in visitor.called_funcs:
        try:
            # 从模块中获取函数对象
            called_func = getattr(f_module, func_name)
            # 解析被调用函数的AST
            called_func_source = inspect.getsource(called_func)
            called_func_ast = ast.parse(called_func_source)
            print(f"\n===== 被调用函数 {func_name} 的AST =====")
            print(ast.dump(called_func_ast, indent=2))
        except (AttributeError, TypeError):
            print(f"无法获取函数 {func_name} 的源码,可能是内置函数或未在当前模块定义")
    
    return f_ast

运行主文件后,就能输出f1的完整AST结构了。

二、获取作用域内变量的值

要获取函数作用域内的变量,需要访问函数执行时的栈帧。你提到的装饰器是非常合适的方案——它可以在函数执行前后安全捕获栈帧信息,避免直接执行函数带来的副作用。

方案:用装饰器捕获栈帧变量

编写一个装饰器,在函数执行时记录局部变量和全局变量,后续可传递给check函数使用:

# 主文件代码
from other import check
import inspect

def track_func_vars(func):
    def wrapper(*args, **kwargs):
        # 获取当前栈帧的上一层(即被装饰函数的执行帧)
        frame = inspect.currentframe().f_back
        # 复制局部变量和全局变量(避免后续栈帧销毁导致变量丢失)
        func._local_vars = frame.f_locals.copy()
        func._global_vars = frame.f_globals.copy()
        # 打印变量信息(也可存储供check函数使用)
        print(f"\n===== 函数 {func.__name__} 的作用域变量 =====")
        print(f"局部变量: {func._local_vars}")
        print(f"全局变量: {func._global_vars}")
        return func(*args, **kwargs)
    return wrapper

def f1(): return 1

@track_func_vars
def f2():
    x = 100
    y = x + 50
    return f1()

# 先执行check分析AST
check(f2)
# 调用f2触发装饰器捕获变量
f2()

如果希望在check函数中直接访问这些变量,只需在check里读取f._local_vars和f._global_vars即可。

注意事项

  • 对于跨模块调用的函数,需要确保被调用模块已被导入,否则getattr会找不到函数对象;
  • 如果函数执行有副作用(比如修改文件、发送请求),直接在check中调用函数捕获变量时要谨慎,建议用装饰器的方式按需触发;
  • 上述CallVisitor仅处理了简单的函数名调用,若需要支持obj.method()这类属性调用,需要扩展visit_Call的逻辑,提取属性链中的方法名。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 18:07:33