Search code examples
pythoncevalpython-c-api

Python C API - How to assign a value to eval expression?


Is it possible to assign a value to an "eval expression" without manipulating the evaluation string? Example: The user writes the expression

"globalPythonArray[10]"

which would evaluate to the current value of item 10 of globalPythonArray. But the goal is, to set the value of item 10 to a new value instead of getting the old value. A dirty workaround would be, to define a temporary variable "newValue" and extend the evaluation string to

"globalPythonArray[10] = newValue"

and compile and evaluate that modified string. Are there some low level Python C API functions that I can use such that I don't have to manipulate the evaluation string?


Solution

  • It's possible to "assign" a value to an eval expression by manipulating its abstract syntax tree (AST). It's not necessary to modify the evaluation string directly and if the type of the new value is not too complicated (e.g. numeric or string), you can hard code it into the AST:

    • Compile eval expression to an AST.
    • Replace Load context of expression at root node by Store.
    • Create a new AST with an Assign statement at the root node.
    • Set target to the expression node of the modified eval AST.
    • Set value to the value.
    • Compile the new AST to byte code and execute it.

    Example:

    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)
    

    Output:

    arr [10  2]
    testClass.arr [ 1 20]
    testClass.x 30
    newVarName 40