无需评估即可解析Python表达式

时间:2018-11-22 11:22:08

标签: python parsing

我有一个用Electron / Typescript编写的应用程序,我需要验证用户输入是否是有效的Python表达式。

例如:

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

我无法为这些表达式生成正确的操作数类型和值。我需要解析表达式的内容,并告诉我是否存在表达式错误。

Python eval()函数解析并评估该表达式。我只需要解析。

有什么需要的吗?

1 个答案:

答案 0 :(得分:2)

您可能希望将完整编译转换为完整的Python代码对象,也可以将其解析为抽象语法树。您可以使用compile() function来实现,也可以只使用ast.parse()来生成树。

解析为AST会标记输入,并输出语法对象树,然后可以对其进行进一步分析或转换。编译为字节码更进一步,使用该AST创建一个Python代码对象,您可以选择使用eval()exec() function执行该对象;请注意,后者总是返回None,可能不是评估表达式代码对象的最佳选择。

eval(string)使用eval(compile(string, "<stdin>", "eval"))来编译代码对象的字符串参数,然后执行它,因此compile(string, "<stdin>", "eval")会给您相同的结果,而无需执行。

如果只有表达式有效,则使用"eval"作为模式;如果要接受完整的Python 语句,则使用"exec"。如果输入不是有效的Python表达式(compile())或无效的语句(ast.parse()),则SyntaxError(和"eval")会引发"exec"异常。 / p>

演示:

>>> 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 subclass