Python C API - 如何给eval表达式赋值?
Python C API - How to assign a value to eval expression?
是否可以在不操纵评估字符串的情况下为 "eval expression" 赋值?示例:用户写入表达式
"globalPythonArray[10]"
这将计算为全局Python数组的第 10 项的当前值。但目标是将项目 10 的值设置为新值而不是获取旧值。一个肮脏的解决方法是定义一个临时变量 "newValue" 并将评估字符串扩展到
"globalPythonArray[10] = newValue"
并编译和评估修改后的字符串。是否有一些我可以使用的低级 Python C API 函数,这样我就不必操作求值字符串?
我会说可能不会,因为访问和存储订阅是不同的操作码:
>>> dis.dis(compile('globalPythonArray[10]', 'a', 'exec'))
1 0 LOAD_NAME 0 (globalPythonArray)
2 LOAD_CONST 0 (10)
4 BINARY_SUBSCR
6 POP_TOP
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
>>> dis.dis(compile('globalPythonArray[10] = myValue', 'a', 'exec'))
1 0 LOAD_NAME 0 (myValue)
2 LOAD_NAME 1 (globalPythonArray)
4 LOAD_CONST 0 (10)
6 STORE_SUBSCR
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
此外,在此处插入有关用户输入和 eval()
的常用警告:
globalPythonArray[__import__('os').system('rm -rf /')]
可以通过操作抽象语法树 (AST) 来 "assign" 为 eval 表达式赋值。没有必要直接修改评估字符串,如果新值的类型不是太复杂(例如数字或字符串),您可以将其硬编码到 AST 中:
- 将 eval 表达式编译成 AST。
- 将根节点表达式的Load上下文替换为Store.
- 在根节点创建一个带有 Assign 语句的新 AST。
- 将target设置为修改后的eval AST的表达式节点。
- 设置值为值。
- 将新的 AST 编译为字节码并执行。
示例:
import ast
import numpy as np
def eval_assign_num(expression, value, global_dict, local_dict):
expr_ast = ast.parse(expression, 'eval', 'eval')
expr_node = expr_ast.body
expr_node.ctx = ast.Store()
assign_ast = ast.Module(body=[
ast.Assign(
targets=[expr_node],
value=ast.Num(n=value)
)
])
ast.fix_missing_locations(assign_ast)
c = compile(assign_ast, 'assign', 'exec')
exec(c, global_dict, local_dict)
class TestClass:
arr = np.array([1, 2])
x = 6
testClass = TestClass()
arr = np.array([1, 2])
eval_assign_num('arr[0]', 10, globals(), locals())
eval_assign_num('testClass.arr[1]', 20, globals(), locals())
eval_assign_num('testClass.x', 30, globals(), locals())
eval_assign_num('newVarName', 40, globals(), locals())
print('arr', arr)
print('testClass.arr', testClass.arr)
print('testClass.x', testClass.x)
print('newVarName', newVarName)
输出:
arr [10 2]
testClass.arr [ 1 20]
testClass.x 30
newVarName 40
是否可以在不操纵评估字符串的情况下为 "eval expression" 赋值?示例:用户写入表达式
"globalPythonArray[10]"
这将计算为全局Python数组的第 10 项的当前值。但目标是将项目 10 的值设置为新值而不是获取旧值。一个肮脏的解决方法是定义一个临时变量 "newValue" 并将评估字符串扩展到
"globalPythonArray[10] = newValue"
并编译和评估修改后的字符串。是否有一些我可以使用的低级 Python C API 函数,这样我就不必操作求值字符串?
我会说可能不会,因为访问和存储订阅是不同的操作码:
>>> dis.dis(compile('globalPythonArray[10]', 'a', 'exec'))
1 0 LOAD_NAME 0 (globalPythonArray)
2 LOAD_CONST 0 (10)
4 BINARY_SUBSCR
6 POP_TOP
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
>>> dis.dis(compile('globalPythonArray[10] = myValue', 'a', 'exec'))
1 0 LOAD_NAME 0 (myValue)
2 LOAD_NAME 1 (globalPythonArray)
4 LOAD_CONST 0 (10)
6 STORE_SUBSCR
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
此外,在此处插入有关用户输入和 eval()
的常用警告:
globalPythonArray[__import__('os').system('rm -rf /')]
可以通过操作抽象语法树 (AST) 来 "assign" 为 eval 表达式赋值。没有必要直接修改评估字符串,如果新值的类型不是太复杂(例如数字或字符串),您可以将其硬编码到 AST 中:
- 将 eval 表达式编译成 AST。
- 将根节点表达式的Load上下文替换为Store.
- 在根节点创建一个带有 Assign 语句的新 AST。
- 将target设置为修改后的eval AST的表达式节点。
- 设置值为值。
- 将新的 AST 编译为字节码并执行。
示例:
import ast
import numpy as np
def eval_assign_num(expression, value, global_dict, local_dict):
expr_ast = ast.parse(expression, 'eval', 'eval')
expr_node = expr_ast.body
expr_node.ctx = ast.Store()
assign_ast = ast.Module(body=[
ast.Assign(
targets=[expr_node],
value=ast.Num(n=value)
)
])
ast.fix_missing_locations(assign_ast)
c = compile(assign_ast, 'assign', 'exec')
exec(c, global_dict, local_dict)
class TestClass:
arr = np.array([1, 2])
x = 6
testClass = TestClass()
arr = np.array([1, 2])
eval_assign_num('arr[0]', 10, globals(), locals())
eval_assign_num('testClass.arr[1]', 20, globals(), locals())
eval_assign_num('testClass.x', 30, globals(), locals())
eval_assign_num('newVarName', 40, globals(), locals())
print('arr', arr)
print('testClass.arr', testClass.arr)
print('testClass.x', testClass.x)
print('newVarName', newVarName)
输出:
arr [10 2]
testClass.arr [ 1 20]
testClass.x 30
newVarName 40