提问人:mimookies 提问时间:8/13/2021 更新时间:8/15/2021 访问量:134
在 if 语句中使用自己的逻辑表达式
use own logic expression in an if statement
问:
我得到了一个用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
答:
如果将其转换为集合,则可以使用二进制运算符:
output = list(set(a) & set(b) | set(c))
评论
not
'not A'
not A
A = ['Ok', 'No']
not A == ['', 'a', 'b', ..., 'aa', 'ab', ...] # every string but 'Ok' and 'No'
not A
['Why', 'Okay']
我们可以使用 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_visit
and
or
其次,在转换为(和转换为)时会有点复杂,因为 Python 如何表示 s 链与 s 链。s 链成为具有多个值的单个节点,而 s 链则嵌套 s,每个节点都有一个左和一个右。比较and
&
or
|
and
&
and
BoolOp
&
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
最后,如果没有更多信息,我们就无法实现 .原因是我们还没有定义一个“话语世界”。特别是,应该评估什么?我看到有两种可能的解决方案:not
not A
- 添加一个额外的参数来指定话语的范围。添加 a 以翻译成诸如话语宇宙在哪里之类的东西。
visit_UnaryOp
not A
set(U) - set(A)
U
- 像集合差值二进制运算符一样对待。在这种情况下,最简单的方法是将表达式预处理为要替换为 的字符串。
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)
你强迫你的用户预先解析他们给你的表达式,但你为自己省去了很多担忧和麻烦。
评论
'A': {'Hi', 'No', 'Yes'}
OR_:[AND_:['A', 'B'] , 'C']
AND_:['A', 'B']
dict['A'].intersection(dict['B'])
dict['A'] & dict['B']
pyparsing