Python Генерировать правдивость из выражения, содержащего числовое значение и компараторы

Я пытаюсь создать тестовые примеры из логического выражения, которое может содержать числовые значения и компараторы, например:

(10 ‹ а) и (с == 5)

таблицы истинности кажутся хорошим подходом, и вывод генерации может выглядеть так:

>  a,  c, result
> 11,  5,   True,
> 11,  4,  False,
>  9,  5,  False,
>  9,  4,  False

Довольно просто сгенерировать таблицу истинности, когда выражение содержит только логические операторы, используя ast.nodevisitor. Я закончил с чем-то вроде этого, который хорошо работает.

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? Спасибо.


person S. Pech    schedule 25.07.2018    source источник
comment
Знаете ли вы переменные в выражении, или вам нужно сначала найти их? Кроме того, можно ли доверять выражению, т. е. не могли бы вы просто eval его?   -  person tobias_k    schedule 25.07.2018
comment
выражению можно доверять, а ожидаемый результат можно оценить с помощью eval. Я не знаю переменных с самого начала, поэтому я создал этого посетителя allVariables, чтобы получить их.   -  person S. Pech    schedule 25.07.2018
comment
Если вы не знаете переменных, откуда берутся значения? (Спрашиваю, потому что в вашем примере они не одинаковы для каждой переменной, поэтому некоторые знания о переменных кажутся вероятными.)   -  person tobias_k    schedule 25.07.2018
comment
В моем примере вывода я установил для переменной значения, которые привели бы к тому, что левая часть операции and будет истинной или ложной, если a = 11, то 10 ‹ a будет True, если a = 9, то 10 ‹ a будет False. Но это могло быть 100 и 0. надеюсь, теперь это более понятно   -  person S. Pech    schedule 25.07.2018


Ответы (1)


Я нашел решение проблемы, мне просто нужно было ограничить использование, чтобы числовое значение не заканчивалось справа от операции, а также ограничить возможность с точки зрения операторов.

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

с примером:

(а ‹ 10) и (с == 5)

Я могу сгенерировать следующий вывод:

{ 0: {'входы': {'a': 9, 'c': 5}, 'ожидание': True}, 1: {'входы': {'a': 9, 'c': 6}, 'ожидание': False}, 2: {'входы': {'a': 11, 'c': 5}, 'ожидание': False}, 3: {'входы': {'a': 11, ' c': 6}, 'ожидание': False} }

не стесняйтесь предлагать любые улучшения этого решения.

person S. Pech    schedule 26.07.2018