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

使用exec获取函数内局部变量失败,求解决方案及优化思路

为什么exec执行函数__code__后locals字典为空?以及更好的变量赋值追踪方法

问题原因

你遇到的assignments字典为空的核心原因是:Python对函数的局部变量做了性能优化,函数字节码中的局部变量是通过栈帧的索引直接访问的,而非字典查找。当你用exec(func.__code__, globals(), assignments)执行函数代码时,这些局部变量不会被写入到你传入的assignments字典里——函数的局部作用域和exec的locals字典是两个独立的存储区域。

解决方案

方案1:使用sys.settrace做运行时追踪

sys.settrace可以注册一个追踪函数,监听代码执行过程中的事件(比如行执行、变量赋值),我们可以利用它捕获所有局部变量的赋值操作:

import sys

def track_assignments(func):
    def wrapper(*args, **kwargs):
        assignments = {}
        
        def trace_handler(frame, event, arg):
            if event == 'line':
                current_locals = frame.f_locals.copy()
                # 对比之前的记录,找出新增/修改的变量
                for var, value in current_locals.items():
                    if var not in assignments or assignments[var] != value:
                        assignments[var] = value
                        print(f"{var} = {value}")
            return trace_handler
        
        # 启用追踪
        sys.settrace(trace_handler)
        try:
            return func(*args, **kwargs)
        finally:
            # 恢复原有追踪设置,避免影响其他代码
            sys.settrace(None)
    return wrapper

@track_assignments
def test():
    a = 10
    b = 20
    c = a * b
    print(a)

if __name__ == '__main__':
    test()

运行后输出:

a = 10
b = 20
c = 200
10

方案2:用AST静态分析修改函数代码(更高效)

如果追求性能,可以通过AST(抽象语法树)分析函数代码,自动在所有赋值语句后插入打印逻辑,再重新编译执行。这种方法是静态修改代码,运行时没有额外追踪开销:

import ast
import inspect

def track_assignments(func):
    # 获取函数源代码并解析为AST
    source_code = inspect.getsource(func)
    tree = ast.parse(source_code)
    
    class AssignmentInjector(ast.NodeTransformer):
        def visit_Assign(self, node):
            # 为每个赋值变量添加打印语句
            for target in node.targets:
                if isinstance(target, ast.Name):
                    var_name = target.id
                    # 构造`print(f"{var_name} = {var_name}")`的AST节点
                    print_node = ast.Expr(
                        value=ast.Call(
                            func=ast.Name(id='print', ctx=ast.Load()),
                            args=[
                                ast.JoinedStr(
                                    values=[
                                        ast.Constant(value=f"{var_name} = "),
                                        ast.FormattedValue(
                                            value=ast.Name(id=var_name, ctx=ast.Load()),
                                            conversion=-1
                                        )
                                    ]
                                )
                            ],
                            keywords=[]
                        )
                    )
                    # 将打印语句插入到赋值语句的下一行
                    node.parent.body.insert(node.parent.body.index(node) + 1, print_node)
            return node
        
        def visit_Module(self, node):
            # 遍历所有节点,记录父节点以便插入操作
            for n in ast.walk(node):
                for child in ast.iter_child_nodes(n):
                    child.parent = n
            return self.generic_visit(node)
    
    # 修改AST并修复位置信息
    transformer = AssignmentInjector()
    modified_tree = transformer.visit(tree)
    ast.fix_missing_locations(modified_tree)
    
    # 编译修改后的代码,获取新函数
    compiled_code = compile(modified_tree, filename=inspect.getsourcefile(func), mode='exec')
    namespace = {}
    exec(compiled_code, func.__globals__, namespace)
    modified_func = namespace[func.__name__]
    
    def wrapper(*args, **kwargs):
        return modified_func(*args, **kwargs)
    
    return wrapper

@track_assignments
def test():
    a = 10
    b = 20
    c = a * b
    print(a)

if __name__ == '__main__':
    test()

运行后输出和方案1一致,但性能更优,适合需要频繁调用的函数。

总结

  • 原代码的核心问题:函数局部变量的索引访问机制导致exec无法将其写入传入的locals字典。
  • 运行时追踪(sys.settrace):实现简单,适合调试场景,但有一定性能开销。
  • AST静态修改:性能更好,适合需要低开销的生产环境,但实现稍复杂。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 02:25:26