【问题标题】:Recursive strategies with additional parameters in Hypothesis假设中具有附加参数的递归策略
【发布时间】:2019-04-18 12:56:12
【问题描述】:

使用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

【问题讨论】:

    标签: python-hypothesis


    【解决方案1】:

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

    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

    (另一方面,这有点复杂 - 如果您的解决方法对您有用,请不要觉得有必要这样做!)

    如果您看到有问题的大小爆炸 - 这应该是非常罕见的,因为自那篇文章撰写以来引擎已经做了很多工作 - 老实说,没有什么可做的。将深度限制贯穿整个事物并在每个步骤中递减它确实可以作为最后的手段,但使用起来并不好。

    【讨论】:

    • 对不起,我当时的问题还不够清楚。在最后一段之前添加了一个段落。
    • 哦,对了 - 获得一个类型良好的树可能需要相互递归定义,幸运的是,这些定义得到了很好的支持。我已经更新了我的答案。
    • 不幸的是,对于我的实际用例,我们也需要将数组大小/形状作为类型的一部分来处理 :) 这个解决方案可以推广到处理它,但我认为以增加复杂性为代价更。如果有机会,我会考虑尝试一下。
    • 我认为此时主要是如何安排代码的问题;没有明显更好的方法可以做到这一点。如果您可以分享详细信息,我很乐意对提议的方法发表评论。否则,我认为你在正确的轨道上:)
    【解决方案2】:

    我现在使用的解决方案是调整生成的树,例如如果在操作需要bool时生成了num树,我还画了一个比较运算符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 的,这意味着更频繁地生成特定种类的树。所以我仍然很乐意看到更好的选择。

    【讨论】:

      猜你喜欢
      • 2021-12-12
      • 1970-01-01
      • 2015-08-04
      • 2012-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多