如果你想使用 Python 的解析器而不是它的求值器,你可以这样做。您必须自己评估表达式,但如果您只需要处理布尔表达式,那将不会太难。无论如何,这是学习如何编写评估器而不必担心解析的合理方法。
要解析表达式,可以使用ast.parse 和mode='eval';这将返回一个 AST 而不评估任何内容。
评估 AST 通常是通过深度优先扫描完成的,这很容易编写,但是 ast 模块带有 NodeVisitor 类,它负责一些细节。您需要创建自己的 NodeVisitor 子类,在其中为每个 AST 节点类型 X 定义名为 visit_X 的方法。这些方法可以返回结果,因此通常评估器只会为特定的 AST 节点类型定义一个访问者方法可以评估;这些方法将使用子类的visit 方法评估每个子节点,并根据需要组合值并返回结果。
这可能不是很清楚,所以我将在下面举一个例子。
重要:AST 节点都记录在Python's library reference manual 中。在编写评估器时,您需要不断地参考该文档。确保您使用的文档版本与您的 Python 版本相对应,因为 AST 结构会不时更改。
这是一个简单的求值器(老实说!它主要是 cmets),它处理布尔运算符 and、if 和 not。它通过在字典中查找变量名称来处理变量(必须在构造评估器对象时提供),并且它知道常量值。任何它不理解的东西都会引发异常,我试图相当严格。
import ast
class BooleanEvaluator(ast.NodeVisitor):
def __init__(self, symtab = None):
'''Create a new Boolean Evaluator.
If you want to allow named variables, give the constructor a
dictionary which maps names to values. Any name not in the
dictionary will provoke a NameError exception, but you could
use a defaultdict to provide a default value (probably False).
You can modify the symbol table after the evaluator is
constructed, if you want to evaluate an expression with different
values.
'''
self.symtab = {} if symtab is None else symtab
# Expression is the top-level AST node if you specify mode='eval'.
# That's not made very clear in the documentation. It's different
# from an Expr node, which represents an expression statement (and
# there are no statements in a tree produced with mode='eval').
def visit_Expression(self, node):
return self.visit(node.body)
# 'and' and 'or' are BoolOp, and the parser collapses a sequence of
# the same operator into a single AST node. The class of the .op
# member identifies the operator, and the .values member is a list
# of expressions.
def visit_BoolOp(self, node):
if isinstance(node.op, ast.And):
return all(self.visit(c) for c in node.values)
elif isinstance(node.op, ast.Or):
return any(self.visit(c) for c in node.values)
else:
# This "shouldn't happen".
raise NotImplementedError(node.op.__doc__ + " Operator")
# 'not' is a UnaryOp. So are a number of other things, like unary '-'.
def visit_UnaryOp(self, node):
if isinstance(node.op, ast.Not):
return not self.visit(node.operand)
else:
# This error can happen. Try using the `~` operator.
raise NotImplementedError(node.op.__doc__ + " Operator")
# Name is a variable name. Technically, we probably should check the
# ctx member, but unless you decide to handle the walrus operator you
# should never see anything other than `ast.Load()` as ctx.
# I didn't check that the symbol table contains a boolean value,
# but you could certainly do that.
def visit_Name(self, node):
try:
return self.symtab[node.id]
except KeyError:
raise NameError(node.id)
# The only constants we're really interested in are True and False,
# but you could extend this to handle other values like 0 and 1
# if you wanted to be more liberal
def visit_Constant(self, node):
if isinstance(node.value, bool):
return node.value
else:
# I avoid calling str on the value in case that executes
# a dunder method.
raise ValueError("non-boolean value")
# The `generic_visit` method is called for any AST node for
# which a specific visitor method hasn't been provided.
def generic_visit(self, node):
raise RuntimeError("non-boolean expression")
这里是如何使用评估器的示例。该函数采用一个表达式,该表达式恰好使用三个变量,必须命名为 x、y 和 z,并生成一个真值表。
import itertools
def truthTable(expr):
evaluator = BooleanEvaluator()
theAST = ast.parse(expr, mode='eval')
print("x y z " + expr)
for x, y, z in itertools.product([True, False], repeat=3):
evaluator.symtab = {'x': x, 'y': y, 'z': z}
print(' '.join("FT"[b] for b in (x, y, z, evaluator.visit(theAST))))
truthTable('x and (y or not z) and (not y or z)')