Python C API - Как назначить значение для выражения eval? - PullRequest
0 голосов
/ 31 августа 2018

Можно ли присвоить значение «выражению eval», не манипулируя строкой оценки? Пример: пользователь пишет выражение

"globalPythonArray[10]"

, которое будет соответствовать текущему значению элемента 10 globalPythonArray. Но цель состоит в том, чтобы установить значение элемента 10 на новое значение вместо получения старого значения. Грязный обходной путь - определить временную переменную "newValue" и расширить строку оценки до

"globalPythonArray[10] = newValue"

и скомпилируйте и оцените эту измененную строку. Есть ли какие-то низкоуровневые API-функции Python C, которые я могу использовать, чтобы мне не приходилось манипулировать строкой оценки?

Ответы [ 2 ]

0 голосов
/ 01 сентября 2018

Можно «присвоить» значение выражению eval, манипулируя его абстрактным синтаксическим деревом (AST). Нет необходимости напрямую изменять строку оценки, и если тип нового значения не слишком сложен (например, числовой или строковый), вы можете жестко закодировать его в AST:

  • Скомпилируйте eval-выражение в AST.
  • Заменить Загрузить контекст выражения в корневом узле на Store .
  • Создайте новый AST с помощью Assign оператора в корневом узле.
  • Установить 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
0 голосов
/ 31 августа 2018

Скорее всего, нет, поскольку для доступа и хранения подписок используются разные коды операций:

>>> 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 /')]
...