【问题标题】:python calculating parsed logical expression (without eval)python计算解析的逻辑表达式(没有eval)
【发布时间】:2022-01-27 14:51:51
【问题描述】:

我正在寻找计算字符串表达式的最佳方法,该字符串表达式包含如下逻辑表达式: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

感谢您的帮助。

【问题讨论】:

  • 你能提供预期的输出吗?我不明白你在追求什么。
  • @theherk 添加了预期的输出
  • 您需要评估您的表达式。如果您不想使用 python 提供的eval,您可以创建自己的令牌提取器和评估器。它会根据操作的优先级将您的表达式转换为树,然后通过解析树来评估它。
  • 如果你不想使用 eval,你可以查看operator 模块。在那里你可以找到and_or_...当你在你的表达中找到正确的词时,你可以使用它们
  • 这能回答你的问题吗? Parsing pseudo-algebraic string into command

标签: python parsing pyparsing


【解决方案1】:

如果你想使用 Python 的解析器而不是它的求值器,你可以这样做。您必须自己评估表达式,但如果您只需要处理布尔表达式,那将不会太难。无论如何,这是学习如何编写评估器而不必担心解析的合理方法。

要解析表达式,可以使用ast.parsemode='eval';这将返回一个 AST 而不评估任何内容。

评估 AST 通常是通过深度优先扫描完成的,这很容易编写,但是 ast 模块带有 NodeVisitor 类,它负责一些细节。您需要创建自己的 NodeVisitor 子类,在其中为每个 AST 节点类型 X 定义名为 visit_X 的方法。这些方法可以返回结果,因此通常评估器只会为特定的 AST 节点类型定义一个访问者方法可以评估;这些方法将使用子类的visit 方法评估每个子节点,并根据需要组合值并返回结果。

这可能不是很清楚,所以我将在下面举一个例子。

重要:AST 节点都记录在Python's library reference manual 中。在编写评估器时,您需要不断地参考该文档。确保您使用的文档版本与您的 Python 版本相对应,因为 AST 结构会不时更改。

这是一个简单的求值器(老实说!它主要是 cmets),它处理布尔运算符 andifnot。它通过在字典中查找变量名称来处理变量(必须在构造评估器对象时提供),并且它知道常量值。任何它不理解的东西都会引发异常,我试图相当严格。

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)')

【讨论】:

  • 哇!感谢您提供非常详细的答案!我仍然需要真正深入了解它,但从测试中我运行它的工作以及我正在寻找的东西。
【解决方案2】:

在我看来,你所追求的 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,但我想我会提供它以防万一。

【讨论】:

  • 标题字面意思是没有eval
  • Eval 在大型项目中的 python 中使用并不是很好:nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
  • 如果不清楚为什么指定的边界条件是相关的,可以给出问题中给出的问题的解决方案。我们可以在 meta.xml 中找到这方面的信息。我保证我的努力会有所帮助。没有具体说明为什么 eval 是不可能的。
  • @EmaIl 如果您不使用 eval 因为它很危险,请使用 ast.literal_eval 安全地评估表达式 docs.python.org/3/library/ast.html#ast.literal_eval
  • 试过的文字 eval 得到了这个错误:ValueError: malformed node or string: <_ast.Compare object at 0x7f80c8f15bb0>True and True 不会帮助我,我正在寻找一种方法来使用可能改变的变量值
猜你喜欢
  • 1970-01-01
  • 2020-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-14
相关资源
最近更新 更多