在返回值满足某些条件之前,如何调用函数序列?

时间:2014-05-02 18:41:29

标签: python design-patterns

有时我会发现自己编写的代码如下:

def analyse(somedata):
    result = bestapproach(somedata)
    if result:
        return result
    else:
        result = notasgood(somedata)
        if result:
            return result
        else:
            result = betterthannothing(somedata)
            if result:
                return result
            else:
                return None

那太难看了。当然,有些人喜欢放弃一些语法:

def analyse(somedata):
    result = bestapproach(somedata)
    if result:
        return result
    result = notasgood(somedata)
    if result:
        return result
    result = betterthannothing(somedata)
    if result:
        return result

但这并没有太大改善;这里仍然有大量重复的代码,它仍然很难看。

我研究了使用带有sentinel值的内置iter(),但在这种情况下,None值用于表示循环应继续运行,而不是哨兵用于表示循环应该终止。

在Python中是否还有其他(理智)技术可以实现这种“继续尝试,直到找到有效的模式”模式?

我应该澄清“返回值满足某些条件”不仅限于条件为if bool(result) is True的情况,如示例中所示。可能的是,可能的分析函数列表各自产生一些衡量成功程度的系数(例如,R平方值),并且您想要设置接受的最小阈值。因此,通用解决方案本身不应该依赖于结果的真值。

3 个答案:

答案 0 :(得分:6)

如果功能数量不是太高,为什么不使用or运算符?

d = 'somedata'
result = f1(d) or f2(d) or f3(d) or f4(d)

它只会应用这些函数,直到其中一个函数返回的内容不是False

答案 1 :(得分:5)

选项#1:使用or

当总函数的数量是a)已知,并且b)很小,并且测试条件完全基于返回的真值时,可以简单地使用or作为Grapsus建议:

d = 'somedata'
result = f1(d) or f2(d) or f3(d) or f4(d)

因为Python's boolean operators short-circuit,函数从右到左执行,直到其中一个函数产生一个评估为True的返回值,此时分配给result,剩下的就是result函数未评估;或者直到你的功能用尽,False被分配result = (r for r in (f(somedata) for f in functions) if <test-condition>).next()


选项#2:使用发电机

当总函数的数量为a)未知,或b)非常大时,单行生成器理解方法有效,如Bitwise建议的那样:

<test-condition>

这比选项#1有额外的优势,你可以使用你想要的任何.next(),而不是只依赖于真值。每次调用f时:

  1. 我们要求外部生成器使用它的下一个元素
  2. 外部生成器向内部生成器请求其下一个元素
  3. 内部生成器从functions请求f(somedata)并尝试评估f
  4. 如果可以计算表达式(即,somedata是函数而f(somedata)是有效的句子),则内部生成器会将<test-condition>的返回值生成到外部生成器< / LI>
  5. 如果满足f(somedata),则外部生成器会返回result的返回值,并将其指定为<test-condition>
  6. 如果在步骤5中未满足.next(),请重复步骤2-4
  7. 这种方法的一个弱点是嵌套的理解可能不如它们的多行等价物直观。此外,如果内部生成器耗尽而没有满足测试条件,StopIteration会引发def analyse(somedata): analysis_functions = [best, okay, poor] for f in analysis_functions: result = f(somedata) if result: return result ,必须处理(在try-except块中)或阻止(通过确保最后一个函数将始终“成功“)。


    选项#3:使用自定义功能

    由于我们可以将可调用函数放在列表中,因此一个选项是按照应该使用的顺序显式列出要“尝试”的函数,然后遍历该列表:

    for ... else

    优点:修复了重复代码的问题,更清楚的是你正在进行迭代过程,并且它发生短路(在找到“好”结果后不继续执行函数)。

    这也可以用Python的def analyse(somedata): analysis_functions = [best, okay, poor] for f in analysis_functions: result = f(somedata) if result: break else: return None return result 语法编写:*

    analyse()

    这里的优点是可以识别退出函数的不同方法,如果您希望None函数完全失败以返回{{1}}以外的其他内容,或者提升例外。否则,它会更长,更深奥。

    *如"Transforming Code into Beautiful, Idiomatic Python"所述,从@ 15:50开始。

答案 2 :(得分:2)

这是非常pythonic:

result = (i for i in (f(somedata) for f in funcs) if i is not None).next()

我们的想法是使用生成器,因此您可以进行延迟评估,而不是评估所有函数。请注意,您可以将condition / funcs更改为您喜欢的任何内容,因此这比Grapsus提出的or解决方案更强大。

这是一个很好的例子,为什么生成器在Python中很强大。

有关其工作原理的更详细说明:

我们要求这个生成器使用单个元素。然后外部生成器向内部生成器(f(d) for f in funcs)请求单个元素,并对其进行求值。如果它通过了条件,那么我们就完成并退出,否则它会继续向内部生成器询问元素。