在 if 语句中使用自己的逻辑表达式

use own logic expression in an if statement

提问人:mimookies 提问时间:8/13/2021 更新时间:8/15/2021 访问量:134

问:

我得到了一个用pyparsing解析的表达式,将其重建为一种布尔树:

expr = '(A and B) or C'
parsed -> OR_:[AND_:['A', 'B'] , 'C']

A、B 和 C 是具有字符串值的字典中的键(没有布尔值!

OR_(联合)和AND_(交集)只是类名,不做任何事情,我正在考虑在这些类中放置一个评估器。

现在我的问题是,如何将这个解析的表达式变成 Python 可以评估的表达式? 我想做的是获取一些字符串值并查看它是否满足整个表达式的条件,或者让它遍历每个子表达式并将其附加到结果列表中。

例:

dict: {'A': ['Hi', 'No', 'Yes'], 'B': ['Why', 'No', 'Okay'], 'C': ['Okay']}
expression = '(A and B) or C'
if value in expression:
 output.append(value)
output -> ['No', 'Okay']  #intersection of A and B, union of that with C

就是这样,但这部分是困扰我的,因为我想不出任何其他方法来写它。if value in expression

python-3.x 运算符 布尔逻辑

评论

0赞 jarmod 8/13/2021
我认为您应该首先使 dict 值实际集合,例如:.然后编写代码来获取解析的表达式并对其进行评估,使用堆栈和传统的 Python 集合操作进行计算,例如,计算结果为 或简单地计算为 .与工会类似。'A': {'Hi', 'No', 'Yes'}OR_:[AND_:['A', 'B'] , 'C']AND_:['A', 'B']dict['A'].intersection(dict['B'])dict['A'] & dict['B']
0赞 Kyle Parsons 8/13/2021
这是特定于使用的东西吗?我不确定您的代码到底要实现什么。pyparsing
0赞 mimookies 8/13/2021
@jarmond啊哈,我明白了,这确实进入了我正在考虑的方向。我只是有点困惑如何结合联合和排除多次迭代它......
0赞 mimookies 8/13/2021
@KyleParsons呃,我不会说它是特定于 pyparsing 的,但 pyparsing 有助于解析我猜的表达式。它不一定是pyparsing,但它是我能想到的唯一解析器。我的目标是输入任何带有和/或/否的表达式,并基于此输出对表达式有效的所有值

答:

0赞 RatajS 8/13/2021 #1

如果将其转换为集合,则可以使用二进制运算符:

output = list(set(a) & set(b) | set(c))

评论

0赞 mimookies 8/13/2021
谢谢你的回答!但我正在寻找更灵活的表达式,因为表达式依赖于用户输入,因此表达式可以是 and/or/not 的任意组合
0赞 Kyle Parsons 8/15/2021
我认为语义是不确定的,除非我们处于我们在某个地方定义的可能性宇宙中。应该评估什么?not'not A'
0赞 mimookies 8/17/2021
@KyleParsons应该导致除 A 中的元素之外的几乎所有元素not A
0赞 Kyle Parsons 8/17/2021
当然,但在什么情况下?如果那么,可能性的宇宙是或是否被隐含地假定为字典中所有值的结合?A = ['Ok', 'No']not A == ['', 'a', 'b', ..., 'aa', 'ab', ...] # every string but 'Ok' and 'No'
0赞 mimookies 8/17/2021
@KyleParsons 哦,对不起,我刚刚注意到我错过了提到 sth,还有另一个列表包含其他列表中的所有元素。基本上在这种情况下,将 ALL 与 A 进行比较并得到结果(不包括来自 A 的元素,而是其他元素)。希望这更有意义:)not A['Why', 'Okay']
1赞 Kyle Parsons 8/15/2021 #2

我们可以使用 ast 模块解析和翻译我们的表达式。我们首先解析我们的语句,然后定义一个节点转换器,它与 、 交换,并将变量名包装在函数中。然后,我们可以编译这个翻译的 ast 并在我们的字典上下文中对其进行评估。and&or|set

import ast
from typing import Dict, Hashable, List, NoReturn, TypeVar

A = TypeVar('A', bound=Hashable)


def evaluate_logic(expr: str, context: Dict[str, List[A]]) -> List[A]:
    tr = ast.parse(expr, mode='eval')
    new_tr = ast.fix_missing_locations(TranslateLogic().visit(tr))
    co = compile(new_tr, filename='', mode='eval')
    return list(eval(co, context))


class TranslateLogic(ast.NodeTransformer):
    def visit_BoolOp(self, node: ast.BoolOp) -> ast.BinOp:
        op = node.op
        new_op = ast.BitAnd() if isinstance(op, ast.And) else ast.BitOr()
        return nested_op(new_op, [self.visit(value) for value in node.values])

    def visit_Name(self, node: ast.Name) -> ast.Call:
        return call_set(node)

    def visit_Expression(self, node: ast.Expression) -> ast.Expression:
        return super().generic_visit(node)

    def generic_visit(self, node: ast.AST) -> NoReturn:
        raise ValueError(f"cannote visit node: {node}")


def nested_op(op, values: List[ast.AST]) -> ast.BinOp:
    if len(values) < 2:
        raise ValueError(f"tried to nest operator with fewer than two values")
    elif len(values) == 2:
        left, right = values
        return ast.BinOp(left=left, op=op, right=right)
    else:
        left, *rest = values
        return ast.BinOp(left=left, op=op, right=nested_op(op, rest))


def call_set(node: ast.Name) -> ast.Call:
    return ast.Call(func=ast.Name(id='set', ctx=node.ctx), args=[node], keywords=[])


if __name__ == '__main__':
    expr = '(A and B) or C'
    context = {'A': ['Hi', 'No', 'Yes'], 'B': ['Why', 'No', 'Okay'], 'C': ['Okay']}

    print(evaluate_logic(expr, context))
    # prints ['No', 'Okay']

我想说的是,这证明了在 Python 中进行通用解析的挑战,然后在 Python 中应用自定义逻辑,即使在利用现有的解析库时也是如此。

几点说明。我们最终会评估用户提供的代码。有一些安全性,因为如果用户提供比 s 和 s 更复杂的东西,应该会引发错误,但我会在生产情况下非常警惕这段代码。generic_visitandor

其次,在转换为(和转换为)时会有点复杂,因为 Python 如何表示 s 链与 s 链。s 链成为具有多个值的单个节点,而 s 链则嵌套 s,每个节点都有一个左和一个右。比较and&or|and&andBoolOp&BinOp

ast.dump(ast.parse('A and B and C', mode='eval'))
# "Expression(body=BoolOp(op=And(), values=[Name(id='A', ctx=Load()), Name(id='B', ctx=Load()), Name(id='C', ctx=Load())]))"

ast.dump(ast.parse('A & B & C', mode='eval'))
# "Expression(body=BinOp(left=BinOp(left=Name(id='A', ctx=Load()), op=BitAnd(), right=Name(id='B', ctx=Load())), op=BitAnd(), right=Name(id='C', ctx=Load())))"

这就解释了为什么我们需要帮助程序函数。nested_op

最后,如果没有更多信息,我们就无法实现 .原因是我们还没有定义一个“话语世界”。特别是,应该评估什么?我看到有两种可能的解决方案:notnot A

  1. 添加一个额外的参数来指定话语的范围。添加 a 以翻译成诸如话语宇宙在哪里之类的东西。visit_UnaryOpnot Aset(U) - set(A)U
  2. 像集合差值二进制运算符一样对待。在这种情况下,最简单的方法是将表达式预处理为要替换为 的字符串。not" not "" - "

综上所述,如果你只是强迫你的用户进入一个更容易使用的界面(对你来说),你可能会为自己省去很多麻烦。类似的东西

from my_module import And, Or

expr = Or(And("A", "B"), "C")
context = {'A': ['Hi', 'No', 'Yes'], 'B': ['Why', 'No', 'Okay'], 'C': ['Okay']}

evaluate_logic(expr, context)

你强迫你的用户预先解析他们给你的表达式,但你为自己省去了很多担忧和麻烦。

评论

0赞 mimookies 8/17/2021
感谢您的解决方案!我仍在尝试理解代码,但它确实完成了我想要的 And 和 Or。由于它根本不使用 pyparsing,我必须看看我是否可以处理掉最初的想法,但我仍然非常感谢:)