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

Air*_*Air 8 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
Run Code Online (Sandbox Code Playgroud)

那太难看了.当然,有些人喜欢省略一些语法:

def analyse(somedata):
    result = bestapproach(somedata)
    if result:
        return result
    result = notasgood(somedata)
    if result:
        return result
    result = betterthannothing(somedata)
    if result:
        return result
Run Code Online (Sandbox Code Playgroud)

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

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

在Python中是否有任何其他(理智)技术用于实现这种"继续尝试,直到找到可行的方式"模式?

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

Air*_*Air 7

选项#1:使用 or

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

d = 'somedata'
result = f1(d) or f2(d) or f3(d) or f4(d)
Run Code Online (Sandbox Code Playgroud)

因为Python的布尔运算符短路,所以函数从右到左执行,直到其中一个产生一个被评估为的返回值True,此时进行赋值result并且不评估其余的函数; 或直到你用完了功能,并被result分配False.


选项#2:使用发电机

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

result = (r for r in (f(somedata) for f in functions) if <test-condition>).next()
Run Code Online (Sandbox Code Playgroud)

与选项#1相比,这具有额外的优势,您可以使用任何<test-condition>您想要的,而不是仅仅依赖于真值.每次.next()都叫:

  1. 我们要求外部生成器使用它的下一个元素
  2. 外部发电机向内部发电机询问其下一个元件
  3. 内部生成器请求ffrom functions并尝试评估f(somedata)
  4. 如果可以计算表达式(即,f是一个函数并且somedata是一个有效的arugment),内部生成器会产生f(somedata)外部生成器的返回值
  5. 如果<test-condition>满足,则外部生成器生成返回值,f(somedata)并将其指定为result
  6. 如果<test-condition>在步骤5中不满意,请重复步骤2-4

这种方法的一个缺点是嵌套的理解可能不如它们的多行等价物直观.此外,如果内部发生器耗尽而没有满足测试条件,则.next()引发StopIteration必须处理的(在try-except块中)或阻止(通过确保最后一个函数将始终"成功").


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

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

def analyse(somedata):
    analysis_functions = [best, okay, poor]
    for f in analysis_functions:
        result = f(somedata)
        if result:
            return result
Run Code Online (Sandbox Code Playgroud)

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

这也可以用Python的for ... else语法编写:*

def analyse(somedata):
    analysis_functions = [best, okay, poor]
    for f in analysis_functions:
        result = f(somedata)
        if result:
            break
    else:
        return None
    return result
Run Code Online (Sandbox Code Playgroud)

这里的优点是可以识别退出函数的不同方法,如果您希望analyse()函数完全失败以返回其他内容None或引发异常,这可能很有用.否则,它会更长,更深奥.

*如"将代码转换为美丽的,惯用的Python"中所述,从15:50开始.


Gra*_*sus 6

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

d = 'somedata'
result = f1(d) or f2(d) or f3(d) or f4(d)
Run Code Online (Sandbox Code Playgroud)

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

  • 如果你真的在做"如果结果",这将是一个很好的方法,但这可能是速记. (2认同)