假设中具有其他参数的递归策略

时间:2019-04-06 11:34:01

标签: python-hypothesis

使用recursive,我可以生成简单的AST,例如

from hypothesis import *
from hypothesis.strategies import *

def trees():
    base = integers(min_value=1, max_value=10).map(lambda n: 'x' + str(n))

    @composite
    def extend(draw, children):
        op = draw(sampled_from(['+', '-', '*', '/']))
        return (op, draw(children), draw(children))

    return recursive(base, draw)

现在,我想更改它,以便除算术运算外还可以生成布尔运算。我最初的想法是向trees添加参数:

def trees(tpe):
    base = integers(min_value=1, max_value=10).map(lambda n: 'x' + str(n) + ': ' + tpe)

    @composite
    def extend(draw, children):
        if tpe == 'bool':
            op = draw(sampled_from(['&&', '||']))
            return (op, draw(children), draw(children))
        elif tpe == 'num':
            op = draw(sampled_from(['+', '-', '*', '/']))
            return (op, draw(children), draw(children))

    return recursive(base, draw)

到目前为止。但是我该如何混合呢?也就是说,我还想要比较运算符和三元运算符,这将需要“使用不同的参数调用children”。

树的类型必须正确:如果操作是'||''&&',则两个参数都必须为布尔值,'+''<'的参数必须为是数字,等等。如果我只有两种类型,可以使用filter(给定type_of函数):

if op in ('&&', '||'):
    bool_trees = children.filter(lambda x: type_of(x) == 'bool')
    return (op, draw(bool_trees), draw(bool_trees))

但在实际情况下,这是不可接受的。

recursive支持吗?还是有另一种方法?显然,我可以递归地直接定义trees,但这遇到了the standard problems

2 个答案:

答案 0 :(得分:2)

您可以简单地描述从任意一组操作中进行比较的树-在这种情况下,可以通过从['&&', '||', '+', '-', '*', '/']进行采样来简单地进行。

def trees():
    return recursive(
        integers(min_value=1, max_value=10).map('x{}'.format),
        lambda node: tuples(sampled_from('&& || + - * /'.split()), node, node)
    )

但是,那当然不是很好的类型(除非偶然发生)。我认为类型正确的AST的最佳选择是:

  1. 为每种类型,定义一种评估该类型树的策略。基本情况只是该类型的值(一种策略)。
  2. 扩展名是使用通过st.deferred的相互递归来预先计算可能产生此类型值的类型和操作的可能组合。看起来像...
bool_strat = deferred(
    lambda: one_of(
        booleans(),
        tuples(sampled_from(["and", "or"], bool_strat, bool_strat), 
        tuples(sampled_from(["==", "!=", "<", ...]), integer_strat, integer_strat),
    )
)
integer_strat = deferred(
    lambda: one_of(
        integers(),
        tuples(sampled_from("= - * /".split()), integer_strat, integer_strat),
    )
)
any_type_ast = bool_strat | integer_strat

它将像魔术般:D

(另一方面,这要复杂得多-如果您的解决方法对您有用,那么您就不必这样做了!)

如果您看到大小爆炸性的问题-这应该非常少见,因为自从撰写该文章以来,该引擎已经进行了大量工作-坦白讲,您无需做太多事情。在整个过程中穿插深度限制并在每个步骤中递减深度限制确实可以作为最后的手段,但是使用它并不好。

答案 1 :(得分:2)

我现在使用的解决方案是调整生成的树,例如如果在操作需要num时生成了bool树,我还将绘制一个比较运算符op和一个常量const并返回(op, tree, const)

def make_bool(tree, draw):
    if type_of(tree) == 'bool':
        return tree
    else type_of(tree) == 'num':
        op = draw(sampled_from(comparison_ops))
        const = draw(integers())
        side = draw(booleans())
        return (op, tree, const) if side else (op, const, tree)

// in def extend:
if tpe == 'bool':
    op = draw(sampled_from(bool_ops + comparison_ops))
    if op in bool_ops:
        return (op, make_bool(draw(children), draw), make_bool(draw(children), draw))
    else:
        return (op, make_num(draw(children), draw), make_num(draw(children), draw))

不幸的是,它是针对AST的,这意味着特定种类的树会更频繁地生成。因此,我仍然很高兴看到更好的选择。