Разбор выражения Python без оценки - PullRequest
0 голосов
/ 22 ноября 2018

У меня есть приложение, написанное на Electron / Typescript, и мне нужно проверить, что пользовательский ввод является допустимым выражением Python.

Например:

  1. cos(PARAMPOLY.engineeringValue1) + cos(PARAMPOLY.engineeringValue2)
  2. x + y + z

У меня нет способа получить правильный тип и значение операндов для этих выражений.Мне нужно что-то, что анализирует выражение и сообщает мне, если есть ошибка выражения.

Функция Python eval() анализирует и оценивает выражение.Мне нужен только разбор.

Есть ли что-то для этого нужно?

1 Ответ

0 голосов
/ 22 ноября 2018

Вы можете захотеть полная компиляция в полный объект кода Python, или вы можете просто проанализировать абстрактное синтаксическое дерево.Вы можете использовать функцию compile() для достижения любого из них или просто использовать ast.parse() для создания дерева.

Синтаксический анализ в AST токенизирует входные и выходные данныедерево синтаксических объектов, которые вы затем можете анализировать или преобразовывать.Компиляция в байт-код идет еще дальше, используя этот AST для создания объекта кода Python, который вы можете по желанию выполнить либо с помощью eval(), либо с помощью exec() function ;обратите внимание, что последний всегда возвращает None и, вероятно, не лучший выбор для оценки объекта кода выражения.

eval(string) использует eval(compile(string, "<stdin>", "eval")) для компиляции строкового аргумента в объект кода, а затем его выполнения,поэтому compile(string, "<stdin>", "eval") даст вам тот же результат без выполнения.

Используйте "eval" в качестве режима, если допустимо только выражение , или "exec", если полный оператор Python должны быть приняты.compile()ast.parse()) вызывает исключение SyntaxError, если ввод не является допустимым выражением Python ("eval") или недопустимыми выражениями ("exec").

Демонстрация:

>>> example1 = "cos(PARAMPOLY.engineeringValue1) + cos(PARAMPOLY.engineeringValue2)"
>>> example2 = "x + y + z"
>>> compile(example1, "<stdin>", "eval")
<code object <module> at 0x111c2eae0, file "<stdin>", line 1>
>>> compile(example2, "<stdin>", "eval")
<code object <module> at 0x111c2e540, file "<stdin>", line 1>
>>> result2 = _
>>> eval(result2, {"x": 42, "y": 81, "z": 117})
240
>>> compile("not a valid expression", "<stdin>", "eval")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1
    not a valid expression
              ^
SyntaxError: invalid syntax

Синтаксический анализ AST позволил бы вам узнать, к каким именам код должен получить доступ;Вы можете собирать имена, ища Name узлов:

>>> import ast
>>> tree1 = ast.parse(example1)
>>> tree2 = ast.parse(example2)
>>> ast.dump(tree2.body[0])
"Expr(value=BinOp(left=Call(func=Name(id='cos', ctx=Load()), args=[Attribute(value=Name(id='PARAMPOLY', ctx=Load()), attr='engineeringValue1', ctx=Load())], keywords=[]), op=Add(), right=Call(func=Name(id='cos', ctx=Load()), args=[Attribute(value=Name(id='PARAMPOLY', ctx=Load()), attr='engineeringValue2', ctx=Load())], keywords=[])))"
>>> ast.dump(tree2.body[0])
"Expr(value=BinOp(left=BinOp(left=Name(id='x', ctx=Load()), op=Add(), right=Name(id='y', ctx=Load())), op=Add(), right=Name(id='z', ctx=Load())))"
>>> {node.id for node in ast.walk(tree1) if isinstance(node, ast.Name)}
{'cos', 'PARAMPOLY'}
>>> {node.id for node in ast.walk(tree2) if isinstance(node, ast.Name)}
{'x', 'z', 'y'}

Обратите внимание, что вышеупомянутый игнорируемый контекст, поэтому имя атрибута PARAMPONLY также указано в списке.Напишите ast.NodeVisitor подкласс , если вам нужно обработать синтаксическое дерево с дополнительным контекстом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...