python 计算已解析的逻辑表达式(没有 eval)
python calculating parsed logical expression (without eval)
我正在寻找计算包含逻辑表达式的字符串表达式的最佳方法,例如:x and not y or (z or w)
或 json 逻辑,例如:{"and": {"x": "true", "y": "false"}, "or": {"or": {"z": "true", "w": "True"}}}
我决定配置文件应该是什么样子,这样格式就可以改变,这只是一个例子。
我看到了一种使用 pyparse
将第一个字符串解析为类似以下内容的方法:['x', 'AND', 'y', 'OR', ['z', 'OR', 'W']]
但我不知道如何在解析后获取表达式的值。
关于 json 逻辑格式 我看到包 json-logic
试过了,但似乎代码被破坏了,因为我不能 运行 他们的任何例子所以如果有是我认为会很棒的其他东西。
x = True
y = False
z = False
w = False
calc_string_as_bool('x and not y or (z or w)') -> True
感谢您的帮助。
在我看来你想要的东西 是 eval,即使你说没有 eval。
In [1]: True and True
Out[1]: True
In [2]: e = "True and True"
In [3]: eval(e)
Out[3]: True
In [4]: el = ["True", "and", "True"]
In [5]: eval(" ".join(el))
Out[5]: True
也许您可以澄清为什么不能选择使用 eval 来执行 eval 的操作。
添加示例,基于相关示例:
def eval_recurse(ops, replacements):
evaluated = []
for op in ops:
if type(op) is list:
evaluated.append(str(eval_recurse(op, replacements)))
continue
if str(op).lower() in ["and", "not", "or"]:
op = op.lower()
if op in replacements:
op = str(replacements[op])
evaluated.append(op)
return eval(" ".join(evaluated))
if __name__ == "__main__":
replacements = {"x": True, "y": False, "z": False, "w": False}
print(eval_recurse(["x", "AND", "NOT", "y", "OR", ["z", "OR", "w"]], replacements))
这会产生 True
。这可能没有帮助,因为您不想使用 eval,但我想我会提供它以防万一。
如果您想使用 Python 的解析器而不是它的求值器,您可以这样做。您必须自己评估表达式,但如果您只需要处理布尔表达式,那不会太困难。无论如何,这是学习如何编写求值器而不必担心解析的合理方法。
要解析表达式,可以使用 ast.parse
和 mode='eval'
;这将 return 一个没有评估任何东西的 AST。
评估 AST 通常是通过 depth-first 扫描完成的,这很容易编写,但是 ast 模块带有 NodeVisitor
class 来处理一些细节。您需要创建自己的 NodeVisitor
的子 class,在其中为每个 AST 节点类型 X 定义名为 visit_X
的方法。这些方法可以 return 结果,因此通常是评估器将为可以评估的特定 AST 节点类型定义访问者方法;这些方法将使用 subclass 的 visit
方法评估每个子节点,适当地组合值,以及 return 结果。
这可能不是很清楚,所以我将在下面举一个例子。
重要提示:AST 节点都记录在 Python's library reference manual 中。在编写评估程序时,您需要经常参考该文档。确保您使用的文档版本与您的 Python 版本相对应,因为 AST 结构确实会不时更改。
这是一个简单的求值器(老实说!主要是注释),它处理布尔运算符 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)的表达式,并生成真值 table.
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)')
我正在寻找计算包含逻辑表达式的字符串表达式的最佳方法,例如:x and not y or (z or w)
或 json 逻辑,例如:{"and": {"x": "true", "y": "false"}, "or": {"or": {"z": "true", "w": "True"}}}
我决定配置文件应该是什么样子,这样格式就可以改变,这只是一个例子。
我看到了一种使用 pyparse
将第一个字符串解析为类似以下内容的方法:['x', 'AND', 'y', 'OR', ['z', 'OR', 'W']]
但我不知道如何在解析后获取表达式的值。
关于 json 逻辑格式 我看到包 json-logic
试过了,但似乎代码被破坏了,因为我不能 运行 他们的任何例子所以如果有是我认为会很棒的其他东西。
x = True
y = False
z = False
w = False
calc_string_as_bool('x and not y or (z or w)') -> True
感谢您的帮助。
在我看来你想要的东西 是 eval,即使你说没有 eval。
In [1]: True and True
Out[1]: True
In [2]: e = "True and True"
In [3]: eval(e)
Out[3]: True
In [4]: el = ["True", "and", "True"]
In [5]: eval(" ".join(el))
Out[5]: True
也许您可以澄清为什么不能选择使用 eval 来执行 eval 的操作。
添加示例,基于相关示例:
def eval_recurse(ops, replacements):
evaluated = []
for op in ops:
if type(op) is list:
evaluated.append(str(eval_recurse(op, replacements)))
continue
if str(op).lower() in ["and", "not", "or"]:
op = op.lower()
if op in replacements:
op = str(replacements[op])
evaluated.append(op)
return eval(" ".join(evaluated))
if __name__ == "__main__":
replacements = {"x": True, "y": False, "z": False, "w": False}
print(eval_recurse(["x", "AND", "NOT", "y", "OR", ["z", "OR", "w"]], replacements))
这会产生 True
。这可能没有帮助,因为您不想使用 eval,但我想我会提供它以防万一。
如果您想使用 Python 的解析器而不是它的求值器,您可以这样做。您必须自己评估表达式,但如果您只需要处理布尔表达式,那不会太困难。无论如何,这是学习如何编写求值器而不必担心解析的合理方法。
要解析表达式,可以使用 ast.parse
和 mode='eval'
;这将 return 一个没有评估任何东西的 AST。
评估 AST 通常是通过 depth-first 扫描完成的,这很容易编写,但是 ast 模块带有 NodeVisitor
class 来处理一些细节。您需要创建自己的 NodeVisitor
的子 class,在其中为每个 AST 节点类型 X 定义名为 visit_X
的方法。这些方法可以 return 结果,因此通常是评估器将为可以评估的特定 AST 节点类型定义访问者方法;这些方法将使用 subclass 的 visit
方法评估每个子节点,适当地组合值,以及 return 结果。
这可能不是很清楚,所以我将在下面举一个例子。
重要提示:AST 节点都记录在 Python's library reference manual 中。在编写评估程序时,您需要经常参考该文档。确保您使用的文档版本与您的 Python 版本相对应,因为 AST 结构确实会不时更改。
这是一个简单的求值器(老实说!主要是注释),它处理布尔运算符 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)的表达式,并生成真值 table.
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)')