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