在 Python 中动态评估简单的布尔逻辑

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2467590/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-04 00:43:38  来源:igfitidea点击:

Dynamically evaluating simple boolean logic in Python

pythontreelogicbooleanboolean-logic

提问by a paid nerd

I've got some dynamically-generated boolean logic expressions, like:

我有一些动态生成的布尔逻辑表达式,例如:

  • (A or B) and (C or D)
  • A or (A and B)
  • A
  • empty - evaluates to True
  • (A 或 B) 和 (C 或 D)
  • A 或 (A 和 B)
  • 一个
  • 空 - 评估为 True

The placeholders get replaced with booleans. Should I,

占位符被替换为布尔值。我是不是该,

  1. Convert this information to a Python expression like True or (True or False)and evalit?
  2. Create a binary tree where a node is either a boolor Conjunction/Disjunctionobject and recursively evaluate it?
  3. Convert it into nested S-expressions and use a Lisp parser?
  4. Something else?
  1. 将此信息转换为 Python 表达式,例如True or (True or False)eval它?
  2. 创建一个二叉树,其中一个节点是一个boolConjunction/Disjunction对象并递归评估它?
  3. 将其转换为嵌套的 S 表达式并使用 Lisp 解析器?
  4. 还有什么?

Suggestions welcome.

欢迎提出建议。

采纳答案by Mike Graham

It shouldn't be difficult at all to write a evaluator that can handle this, for example using pyparsing. You only have a few operations to handle (and, or, and grouping?), so you should be able to parse and evaluate it yourself.

编写一个可以处理这个问题的评估器应该不难,例如使用pyparsing。您只需要处理几个操作(和、或和分组?),因此您应该能够自己解析和评估它。

You shouldn't need to explicitly form the binary tree to evaluate the expression.

您不需要显式地形成二叉树来评估表达式。

回答by mlvljr

Here's a small (possibly, 74 lines including whitespace) module I built in about an hour and a half (plus almost an hour to refactoring):

这是我用大约一个半小时(加上将近一个小时的重构时间)构建的一个小(可能有 74 行,包括空格)模块:

str_to_token = {'True':True,
                'False':False,
                'and':lambda left, right: left and right,
                'or':lambda left, right: left or right,
                '(':'(',
                ')':')'}

empty_res = True


def create_token_lst(s, str_to_token=str_to_token):
    """create token list:
    'True or False' -> [True, lambda..., False]"""
    s = s.replace('(', ' ( ')
    s = s.replace(')', ' ) ')

    return [str_to_token[it] for it in s.split()]


def find(lst, what, start=0):
    return [i for i,it in enumerate(lst) if it == what and i >= start]


def parens(token_lst):
    """returns:
        (bool)parens_exist, left_paren_pos, right_paren_pos
    """
    left_lst = find(token_lst, '(')

    if not left_lst:
        return False, -1, -1

    left = left_lst[-1]

    #can not occur earlier, hence there are args and op.
    right = find(token_lst, ')', left + 4)[0]

    return True, left, right


def bool_eval(token_lst):
    """token_lst has length 3 and format: [left_arg, operator, right_arg]
    operator(left_arg, right_arg) is returned"""
    return token_lst[1](token_lst[0], token_lst[2])


def formatted_bool_eval(token_lst, empty_res=empty_res):
    """eval a formatted (i.e. of the form 'ToFa(ToF)') string"""
    if not token_lst:
        return empty_res

    if len(token_lst) == 1:
        return token_lst[0]

    has_parens, l_paren, r_paren = parens(token_lst)

    if not has_parens:
        return bool_eval(token_lst)

    token_lst[l_paren:r_paren + 1] = [bool_eval(token_lst[l_paren+1:r_paren])]

    return formatted_bool_eval(token_lst, bool_eval)


def nested_bool_eval(s):
    """The actual 'eval' routine,
    if 's' is empty, 'True' is returned,
    otherwise 's' is evaluated according to parentheses nesting.
    The format assumed:
        [1] 'LEFT OPERATOR RIGHT',
        where LEFT and RIGHT are either:
                True or False or '(' [1] ')' (subexpression in parentheses)
    """
    return formatted_bool_eval(create_token_lst(s))

The simple tests give:

简单的测试给出:

>>> print nested_bool_eval('')
True
>>> print nested_bool_eval('False')
False
>>> print nested_bool_eval('True or False')
True
>>> print nested_bool_eval('True and False')
False
>>> print nested_bool_eval('(True or False) and (True or False)')
True
>>> print nested_bool_eval('(True or False) and (True and False)')
False
>>> print nested_bool_eval('(True or False) or (True and False)')
True
>>> print nested_bool_eval('(True and False) or (True and False)')
False
>>> print nested_bool_eval('(True and False) or (True and (True or False))')
True

[Partially off-topic possibly]

[可能部分离题]

Note, the you can easily configure the tokens (both operands and operators) you use with the poor-mans dependency-injection means provided (token_to_char=token_to_charand friends) to have multiple different evaluators at the same time (just resetting the "injected-by-default" globals will leave you with a single behavior).

请注意,您可以轻松地配置与提供的穷人依赖注入方法(token_to_char=token_to_char和朋友)一起使用的令牌(操作数和运算符)以同时拥有多个不同的评估器(只需重置“默认注入" 全局变量会给你一个单一的行为)。

For example:

例如:

def fuzzy_bool_eval(s):
    """as normal, but:
    - an argument 'Maybe' may be :)) present
    - algebra is:
    [one of 'True', 'False', 'Maybe'] [one of 'or', 'and'] 'Maybe' -> 'Maybe'
    """
    Maybe = 'Maybe' # just an object with nice __str__

    def or_op(left, right):
        return (Maybe if Maybe in [left, right] else (left or right))

    def and_op(left, right):
        args = [left, right]

        if Maybe in args:
            if True in args:
                return Maybe # Maybe and True -> Maybe
            else:
                return False # Maybe and False -> False

        return left and right

    str_to_token = {'True':True,
                    'False':False,
                    'Maybe':Maybe,
                    'and':and_op,
                    'or':or_op,
                    '(':'(',
                    ')':')'}

    token_lst = create_token_lst(s, str_to_token=str_to_token)

    return formatted_bool_eval(token_lst)

gives:

给出:

>>> print fuzzy_bool_eval('')
True
>>> print fuzzy_bool_eval('Maybe')
Maybe
>>> print fuzzy_bool_eval('True or False')
True
>>> print fuzzy_bool_eval('True or Maybe')
Maybe
>>> print fuzzy_bool_eval('False or (False and Maybe)')
False

回答by Ignacio Vazquez-Abrams

If you set up dicts with the locals and globals you care about then you should be able to safely pass them along with the expression into eval().

如果您使用您关心的局部变量和全局变量设置 dicts,那么您应该能够安全地将它们与表达式一起传递到eval().

回答by Shnatsel

Sounds like a piece of cake using SymPy logic module. They even have an example of that on the docs: http://docs.sympy.org/0.7.1/modules/logic.html

使用 SymPy 逻辑模块听起来像小菜一碟。他们甚至在文档中有一个例子:http: //docs.sympy.org/0.7.1/modules/logic.html

回答by aripy887

I am writing this because I had a solve a similar problem today and I was here when I was looking for clues. (Boolean parser with arbitrary string tokens that get converted to boolean values later).

我写这篇文章是因为我今天解决了一个类似的问题,而我在寻找线索的时候就在这里。(具有任意字符串标记的布尔解析器,稍后将转换为布尔值)。

After considering different options (implementing a solution myself or use some package), I settled on using Lark, https://github.com/lark-parser/lark

在考虑了不同的选择(自己实现解决方案或使用一些包)后,我决定使用 Lark,https://github.com/lark-parser/lark

It's easy to use and pretty fast if you use LALR(1)

如果您使用 LALR(1),则它易于使用且速度非常快

Here is an example that could match your syntax

这是一个可以匹配您的语法的示例

from lark import Lark, Tree, Transformer

base_parser = Lark("""
    expr: and_expr
        | or_expr
    and_expr: token
            | "(" expr ")"
            | and_expr " " and " " and_expr
    or_expr: token
            | "(" expr ")"
            | or_expr " " or " " or_expr
    token: LETTER
    and: "and"
    or: "or"
    LETTER: /[A-Z]+/
""", start="expr")


class Cleaner(Transformer):
    def expr(self, children):
        num_children = len(children)
        if num_children == 1:
            return children[0]
        else:
            raise RuntimeError()

    def and_expr(self, children):
        num_children = len(children)
        if num_children == 1:
            return children[0]
        elif num_children == 3:
            first, middle, last = children
            return Tree(data="and_expr", children=[first, last])
        else:
            raise RuntimeError()

    def or_expr(self, children):
        num_children = len(children)
        if num_children == 1:
            return children[0]
        elif num_children == 3:
            first, middle, last = children
            return Tree(data="or_expr", children=[first, last])
        else:
            raise RuntimeError()


def get_syntax_tree(expression):
    return Cleaner().transform(base_parser.parse(expression))

print(get_syntax_tree("A and (B or C)").pretty())

Note: the regex I chose doesn't match the empty string on purpose (Lark for some reason doesn't allow it).

注意:我选择的正则表达式故意与空字符串不匹配(Lark 出于某种原因不允许这样做)。