通过ast.parse、compile、exec使用self访问实例变量的问题排查
对象绑定自定义表达式实现方案
预设实现流程
- 存储用户输入的表达式字符串
- 使用
ast.parse解析表达式生成AST - 借助
ast.NodeTransformer将表达式内的相关名称节点替换为实例属性访问节点,例如将表达式val1 > val2转换为self.val1 > self.val2 - 将处理完成的AST编译后存储在对应对象上
- 业务需要时调用执行编译后的AST对象
现存问题
实现过程中无法正确关联对象的self引用,测试代码运行未得到预期输出,已确认构造的属性节点语法结构正确,问题出在self引用传递环节。
测试代码:
import ast class test_obj: def __init__(self) -> None: self.value = 0 def get_value(self): tree = ast.parse(ast.parse("self.value", "exec")) exe = compile(ast.parse("self.value", "exec"), "", "exec") print(f"Normal access: {self.value}") print(f"Tree dump: {ast.dump(tree)}") print(f"Exec: {exec(exe)}") a = test_obj() a.get_value()
运行输出:
Normal access: 0 Tree dump: Module(body=[Expr(value=Attribute(value=Name(id='self', ctx=Load()), attr='value', ctx=Load()))], type_ignores=[]) Exec: None
问题根因
exec/eval执行代码时使用独立命名空间,不会自动继承当前函数作用域的self变量。AST中引用的self没有被显式传入执行命名空间,解释器无法定位到对应的实例对象。exec()函数返回值固定为None,即使代码内的表达式能正常计算,结果也不会作为exec的返回值返回。- 测试代码中存在冗余的重复
ast.parse调用,无实际作用。
修正实现
1. 实现AST名称替换逻辑
通过ast.NodeTransformer将需要访问实例属性的裸名称替换为self属性访问节点,转换完成后调用ast.fix_missing_locations补全新增节点的位置信息,避免编译报错:
class SelfAttributeTransformer(ast.NodeTransformer): def visit_Name(self, node): # 跳过内置常量、关键字名称,可根据业务需求调整黑白名单 if node.id in {"True", "False", "None", "and", "or", "not"}: return node # 将裸变量x替换为self.x的属性访问 return ast.Attribute( value=ast.Name(id='self', ctx=ast.Load()), attr=node.id, ctx=node.ctx )
2. 选择合适的编译模式
- 若用户输入为单行求值表达式(如比较、运算类逻辑),使用
eval模式编译,执行后可直接获取表达式返回值 - 若用户输入为多行执行逻辑,使用
exec模式编译,执行时通过命名空间变量传递执行结果
3. 执行时显式注入self引用
调用exec/eval时,将当前实例对象绑定到命名空间的self键上,让AST中的self引用正确指向实例。
修正后的可运行示例:
class test_obj: def __init__(self) -> None: self.value = 0 # 模拟用户输入表达式 user_expr = "value > -1" # 解析、转换、编译AST ast_tree = ast.parse(user_expr, mode="eval") ast_tree = SelfAttributeTransformer().visit(ast_tree) ast.fix_missing_locations(ast_tree) self.compiled_expr = compile(ast_tree, "", "eval") def get_value(self): # 执行时传入self到命名空间 expr_result = eval(self.compiled_expr, {"self": self}) print(f"Normal access: {self.value}") print(f"Expr result: {expr_result}") a = test_obj() a.get_value()
运行输出:
Normal access: 0 Expr result: True
注意:生产环境使用时必须增加AST节点白名单校验,仅允许安全的语法节点存在,避免用户输入恶意代码触发任意代码执行风险。
内容的提问来源于stack exchange,提问作者alenzo
相关产品推荐
相关产品推荐

