如何在 python3 中使用 AST 递归简化数学表达式?
How to recursively simplify a mathematical expression with AST in python3?
我有这个数学表达式:
tree = ast.parse('1 + 2 + 3 + x')
对应这个抽象语法树:
Module(body=[Expr(value=BinOp(left=BinOp(left=BinOp(left=Num(n=1), op=Add(), right=Num(n=2)), op=Add(), right=Num(n=3)), op=Add(), right=Name(id='x', ctx=Load())))])
我想简化它 - 也就是说,得到这个:
Module(body=[Expr(value=BinOp(left=Num(n=6), op=Add(), right=Name(id='x', ctx=Load())))])
根据 the documentation,我应该使用 NodeTransformer class。文档中的建议如下:
Keep in mind that if the node you’re operating on has child nodes you
must either transform the child nodes yourself or call the
generic_visit() method for the node first.
我尝试实现自己的转换器:
class Evaluator(ast.NodeTransformer):
def visit_BinOp(self, node):
print('Evaluating ', ast.dump(node))
for child in ast.iter_child_nodes(node):
self.visit(child)
if type(node.left) == ast.Num and type(node.right) == ast.Num:
print(ast.literal_eval(node))
return ast.copy_location(ast.Subscript(value=ast.literal_eval(node)), node)
else:
return node
在这种情况下它应该做的是将1+2简化为3,然后将3+3简化为6。
它确实简化了我想简化的二元运算,但它并没有更新原来的语法树。我尝试了不同的方法,但我仍然不明白如何递归地简化所有二进制操作(以深度优先的方式)。谁能指出我正确的方向?
谢谢。
visit_*
方法有三个可能的 return 值:
None
表示该节点将被删除,
node
(节点本身)这意味着不会应用任何更改,
- 一个新节点,它将取代旧节点。
因此,当您想用 Num
替换 BinOp
时,您需要 return 一个新的 Num
节点。表达式的计算不能通过 ast.literal_eval
完成,因为这个函数只计算文字(不是任意表达式)。例如,您可以使用 eval
。
所以你可以使用下面的节点转换器class:
import ast
class Evaluator(ast.NodeTransformer):
ops = {
ast.Add: '+',
ast.Sub: '-',
ast.Mult: '*',
ast.Div: '/',
# define more here
}
def visit_BinOp(self, node):
self.generic_visit(node)
if isinstance(node.left, ast.Num) and isinstance(node.right, ast.Num):
# On Python <= 3.6 you can use ast.literal_eval.
# value = ast.literal_eval(node)
value = eval(f'{node.left.n} {self.ops[type(node.op)]} {node.right.n}')
return ast.Num(n=value)
return node
tree = ast.parse('1 + 2 + 3 + x')
tree = ast.fix_missing_locations(Evaluator().visit(tree))
print(ast.dump(tree))
我有这个数学表达式:
tree = ast.parse('1 + 2 + 3 + x')
对应这个抽象语法树:
Module(body=[Expr(value=BinOp(left=BinOp(left=BinOp(left=Num(n=1), op=Add(), right=Num(n=2)), op=Add(), right=Num(n=3)), op=Add(), right=Name(id='x', ctx=Load())))])
我想简化它 - 也就是说,得到这个:
Module(body=[Expr(value=BinOp(left=Num(n=6), op=Add(), right=Name(id='x', ctx=Load())))])
根据 the documentation,我应该使用 NodeTransformer class。文档中的建议如下:
Keep in mind that if the node you’re operating on has child nodes you must either transform the child nodes yourself or call the generic_visit() method for the node first.
我尝试实现自己的转换器:
class Evaluator(ast.NodeTransformer):
def visit_BinOp(self, node):
print('Evaluating ', ast.dump(node))
for child in ast.iter_child_nodes(node):
self.visit(child)
if type(node.left) == ast.Num and type(node.right) == ast.Num:
print(ast.literal_eval(node))
return ast.copy_location(ast.Subscript(value=ast.literal_eval(node)), node)
else:
return node
在这种情况下它应该做的是将1+2简化为3,然后将3+3简化为6。 它确实简化了我想简化的二元运算,但它并没有更新原来的语法树。我尝试了不同的方法,但我仍然不明白如何递归地简化所有二进制操作(以深度优先的方式)。谁能指出我正确的方向?
谢谢。
visit_*
方法有三个可能的 return 值:
None
表示该节点将被删除,node
(节点本身)这意味着不会应用任何更改,- 一个新节点,它将取代旧节点。
因此,当您想用 Num
替换 BinOp
时,您需要 return 一个新的 Num
节点。表达式的计算不能通过 ast.literal_eval
完成,因为这个函数只计算文字(不是任意表达式)。例如,您可以使用 eval
。
所以你可以使用下面的节点转换器class:
import ast
class Evaluator(ast.NodeTransformer):
ops = {
ast.Add: '+',
ast.Sub: '-',
ast.Mult: '*',
ast.Div: '/',
# define more here
}
def visit_BinOp(self, node):
self.generic_visit(node)
if isinstance(node.left, ast.Num) and isinstance(node.right, ast.Num):
# On Python <= 3.6 you can use ast.literal_eval.
# value = ast.literal_eval(node)
value = eval(f'{node.left.n} {self.ops[type(node.op)]} {node.right.n}')
return ast.Num(n=value)
return node
tree = ast.parse('1 + 2 + 3 + x')
tree = ast.fix_missing_locations(Evaluator().visit(tree))
print(ast.dump(tree))