Python 从包含数值和比较器的表达式生成真值表
Python Generate truthtable from expression containing Numeric value and comparators
我正在尝试从可能包含数值和比较器的布尔表达式生成测试用例,例如:
(10 < a) and (c == 5)
truth tables 似乎是一个不错的方法,生成的输出可能如下所示:
> a, c, result
> 11, 5, True,
> 11, 4, False,
> 9, 5, False,
> 9, 4, False
当表达式仅包含使用 ast.nodevisitor 的布尔运算符时,生成真值 table 非常简单。我最终得到了这样的东西,效果很好。
class Expr():
def __init__(self, expr):
self.tree = ast.parse(expr)
self.expr=expr
self.vars = self.allVariables().visit(self.tree)
self.generateTruthTable()
def generateTruthTable(self):
NO_GLOBALS = {'__builtins__': {}}
self.truthtable=dict()
for i, vals in enumerate(product([True,False],repeat=len(self.vars))):
self.truthtable[i] = dict()
self.truthtable[i]['inputs'] = dict(zip(self.vars, vals))
self.truthtable[i]['expectation'] = eval(self.expr, NO_GLOBALS, self.truthtable[i]['inputs'])
class allVariables(ast.NodeVisitor):
def visit_Module(self, node):
self.names = set()
self.generic_visit(node)
return sorted(self.names)
def visit_Name(self, node):
self.names.add(node.id)
现在,无论操作员是什么,我都在为如何执行同一代而苦苦挣扎。看起来递归是这里的关键。有没有办法使用 python AST 执行此类生成?
谢谢。
我找到了解决问题的方法,我只需要限制使用以防止数值在运算符的右侧结束,并且还限制了运算符的可能性。
import ast
import operator
from itertools import product
class OperatorError(Exception):
pass
class Expr():
BOOLOP_SYMBOLS = (
ast.And,
ast.Or
)
UNARYOP_SYMBOLS = (
ast.Not
)
BINOP_SYMBOLS = (
ast.BitAnd,
ast.BitXor,
ast.Pow
)
CMPOP_SYMBOLS = (
ast.Eq,
ast.Gt,
ast.GtE,
ast.Lt,
ast.LtE,
ast.NotEq
)
def __init__(self, expr):
self.tree = ast.parse(expr)
self.expr=expr
self.vars = self.SyntaxChecker().visit(self.tree)
self.tt = self.generateTruthTable()
def generateTruthTable(self):
NO_GLOBALS = {'__builtins__': {}}
truthtable=dict()
for i, vals in enumerate(product([True,False],repeat=len(self.vars.keys()))):
truthtable[i] = dict()
vals_convert=[]
for j,k in enumerate(self.vars.iterkeys()):
vals_convert.append(self.vars[k][vals[j]])
truthtable[i]['inputs'] = dict(zip(self.vars.keys(), vals_convert))
truthtable[i]['expectation'] = eval(self.expr, NO_GLOBALS, truthtable[i]['inputs'])
return truthtable
class SyntaxChecker(ast.NodeVisitor):
def visit_Module(self, module):
self.vars = dict()
self.generic_visit(module)
return self.vars
def visit_Expr(self, expr):
return self.visit(expr.value)
def visit_BoolOp(self, boolop):
if isinstance(boolop.op, Expr.BOOLOP_SYMBOLS):
left,right = map(self.visit, boolop.values)
return [left, right]
else:
raise OperatorError(Expr.BOOLOP_SYMBOLS[boolop.op] + ' is not supported.')
def visit_BinOp(self, binop):
if isinstance(binop.op, Expr.BINOP_SYMBOLS):
left , right = map(self.visit, [binop.left, binop.right])
if isinstance(binop.op, ast.BitAnd):
self.vars[left] = {True: right, False: 0}
if isinstance(binop.op, ast.BitXor):
self.vars[left] = {True: 0, False: right}
if isinstance(binop.op, ast.Pow):
return left ** right
else:
raise OperatorError(Expr.BINOP_SYMBOLS[binop.op] + ' is not supported.')
def visit_Compare(self, cmpop):
if isinstance(cmpop.ops[0], Expr.CMPOP_SYMBOLS):
left = self.visit(cmpop.left)
right = self.visit(cmpop.comparators[0])
if isinstance(cmpop.ops[0], ast.Gt):
self.vars[left] = {True: right + 1, False: right - 1}
elif isinstance(cmpop.ops[0], ast.GtE):
self.vars[left] = {True: right, False: right - 1}
elif isinstance(cmpop.ops[0], ast.Lt):
self.vars[left] = {True: right-1, False: right + 1}
elif isinstance(cmpop.ops[0], ast.LtE):
self.vars[left] = {True: right, False: right + 1}
elif isinstance(cmpop.ops[0], ast.Eq):
self.vars[left] = {True: right, False: right + 1}
elif isinstance(cmpop.ops[0], ast.NotEq):
self.vars[left] = {True: right + 1, False: right}
else:
raise OperatorError(Expr.CMPOP_SYMBOLS[cmpop.ops] + ' is not supported.')
def visit_UnaryOp(self, unaryop):
if isinstance(unaryop.op, Expr.UNARYOP_SYMBOLS):
right= self.visit(unaryop.operand)
self.vars[right] = {True: False, False: True}
else:
raise OperatorError(Expr.UNARYOP_SYMBOLS[unaryop.op] + ' is not supported.')
def visit_Num(self, num):
return num.n
def visit_Name(self, node):
self.vars[node.id] = {True: True, False: False}
return node.id
举例:
(a < 10) and (c == 5)
我可以生成以下输出:
{
0: {'inputs': {'a': 9, 'c': 5}, 'expectation': True},
1: {'inputs': {'a': 9, 'c': 6}, 'expectation': False},
2: {'inputs': {'a': 11, 'c': 5}, 'expectation': False},
3: {'inputs': {'a': 11, 'c': 6}, 'expectation': False} }
随时提出对此解决方案的任何改进建议。
我正在尝试从可能包含数值和比较器的布尔表达式生成测试用例,例如:
(10 < a) and (c == 5)
truth tables 似乎是一个不错的方法,生成的输出可能如下所示:
> a, c, result
> 11, 5, True,
> 11, 4, False,
> 9, 5, False,
> 9, 4, False
当表达式仅包含使用 ast.nodevisitor 的布尔运算符时,生成真值 table 非常简单。我最终得到了这样的东西,效果很好。
class Expr():
def __init__(self, expr):
self.tree = ast.parse(expr)
self.expr=expr
self.vars = self.allVariables().visit(self.tree)
self.generateTruthTable()
def generateTruthTable(self):
NO_GLOBALS = {'__builtins__': {}}
self.truthtable=dict()
for i, vals in enumerate(product([True,False],repeat=len(self.vars))):
self.truthtable[i] = dict()
self.truthtable[i]['inputs'] = dict(zip(self.vars, vals))
self.truthtable[i]['expectation'] = eval(self.expr, NO_GLOBALS, self.truthtable[i]['inputs'])
class allVariables(ast.NodeVisitor):
def visit_Module(self, node):
self.names = set()
self.generic_visit(node)
return sorted(self.names)
def visit_Name(self, node):
self.names.add(node.id)
现在,无论操作员是什么,我都在为如何执行同一代而苦苦挣扎。看起来递归是这里的关键。有没有办法使用 python AST 执行此类生成? 谢谢。
我找到了解决问题的方法,我只需要限制使用以防止数值在运算符的右侧结束,并且还限制了运算符的可能性。
import ast
import operator
from itertools import product
class OperatorError(Exception):
pass
class Expr():
BOOLOP_SYMBOLS = (
ast.And,
ast.Or
)
UNARYOP_SYMBOLS = (
ast.Not
)
BINOP_SYMBOLS = (
ast.BitAnd,
ast.BitXor,
ast.Pow
)
CMPOP_SYMBOLS = (
ast.Eq,
ast.Gt,
ast.GtE,
ast.Lt,
ast.LtE,
ast.NotEq
)
def __init__(self, expr):
self.tree = ast.parse(expr)
self.expr=expr
self.vars = self.SyntaxChecker().visit(self.tree)
self.tt = self.generateTruthTable()
def generateTruthTable(self):
NO_GLOBALS = {'__builtins__': {}}
truthtable=dict()
for i, vals in enumerate(product([True,False],repeat=len(self.vars.keys()))):
truthtable[i] = dict()
vals_convert=[]
for j,k in enumerate(self.vars.iterkeys()):
vals_convert.append(self.vars[k][vals[j]])
truthtable[i]['inputs'] = dict(zip(self.vars.keys(), vals_convert))
truthtable[i]['expectation'] = eval(self.expr, NO_GLOBALS, truthtable[i]['inputs'])
return truthtable
class SyntaxChecker(ast.NodeVisitor):
def visit_Module(self, module):
self.vars = dict()
self.generic_visit(module)
return self.vars
def visit_Expr(self, expr):
return self.visit(expr.value)
def visit_BoolOp(self, boolop):
if isinstance(boolop.op, Expr.BOOLOP_SYMBOLS):
left,right = map(self.visit, boolop.values)
return [left, right]
else:
raise OperatorError(Expr.BOOLOP_SYMBOLS[boolop.op] + ' is not supported.')
def visit_BinOp(self, binop):
if isinstance(binop.op, Expr.BINOP_SYMBOLS):
left , right = map(self.visit, [binop.left, binop.right])
if isinstance(binop.op, ast.BitAnd):
self.vars[left] = {True: right, False: 0}
if isinstance(binop.op, ast.BitXor):
self.vars[left] = {True: 0, False: right}
if isinstance(binop.op, ast.Pow):
return left ** right
else:
raise OperatorError(Expr.BINOP_SYMBOLS[binop.op] + ' is not supported.')
def visit_Compare(self, cmpop):
if isinstance(cmpop.ops[0], Expr.CMPOP_SYMBOLS):
left = self.visit(cmpop.left)
right = self.visit(cmpop.comparators[0])
if isinstance(cmpop.ops[0], ast.Gt):
self.vars[left] = {True: right + 1, False: right - 1}
elif isinstance(cmpop.ops[0], ast.GtE):
self.vars[left] = {True: right, False: right - 1}
elif isinstance(cmpop.ops[0], ast.Lt):
self.vars[left] = {True: right-1, False: right + 1}
elif isinstance(cmpop.ops[0], ast.LtE):
self.vars[left] = {True: right, False: right + 1}
elif isinstance(cmpop.ops[0], ast.Eq):
self.vars[left] = {True: right, False: right + 1}
elif isinstance(cmpop.ops[0], ast.NotEq):
self.vars[left] = {True: right + 1, False: right}
else:
raise OperatorError(Expr.CMPOP_SYMBOLS[cmpop.ops] + ' is not supported.')
def visit_UnaryOp(self, unaryop):
if isinstance(unaryop.op, Expr.UNARYOP_SYMBOLS):
right= self.visit(unaryop.operand)
self.vars[right] = {True: False, False: True}
else:
raise OperatorError(Expr.UNARYOP_SYMBOLS[unaryop.op] + ' is not supported.')
def visit_Num(self, num):
return num.n
def visit_Name(self, node):
self.vars[node.id] = {True: True, False: False}
return node.id
举例:
(a < 10) and (c == 5)
我可以生成以下输出:
{ 0: {'inputs': {'a': 9, 'c': 5}, 'expectation': True}, 1: {'inputs': {'a': 9, 'c': 6}, 'expectation': False}, 2: {'inputs': {'a': 11, 'c': 5}, 'expectation': False}, 3: {'inputs': {'a': 11, 'c': 6}, 'expectation': False} }
随时提出对此解决方案的任何改进建议。