我有一个方法,按顺序调用其他4个方法来检查特定的条件,并立即返回(不检查以下那些)每当一个返回一些真理。

def check_all_conditions():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

这似乎是一大堆行李规定。而不是每个2行if语句,我宁愿这样做:

x and return x

但这是无效的Python。我是不是错过了一个简单、优雅的解决方案?顺便说一句,在这种情况下,这四个检查方法可能代价很高,所以我不想多次调用它们。


当前回答

我在过去看到过一些有趣的switch/case语句的dicts实现,这让我得出了这个答案。使用您提供的示例,您将得到以下结果。(使用complete_sentences_for_function_names非常疯狂,因此check_all_conditions被重命名为status。参见(1))

def status(k = 'a', s = {'a':'b','b':'c','c':'d','d':None}) :
  select = lambda next, test : test if test else next
  d = {'a': lambda : select(s['a'], check_size()  ),
       'b': lambda : select(s['b'], check_color() ),
       'c': lambda : select(s['c'], check_tone()  ),
       'd': lambda : select(s['d'], check_flavor())}
  while k in d : k = d[k]()
  return k

select函数消除了两次调用每个check_FUNCTION的需要,即通过添加另一个函数层,如果check_FUNCTION() else,则避免check_FUNCTION()。这对于长时间运行的函数很有用。dict中的lambdas将其值的执行延迟到while循环。

作为奖励,您可以修改执行顺序,甚至通过更改k和s跳过一些测试,例如k='c',s={'c':'b','b':None}减少测试的数量并反转原始的处理顺序。

那些花时间的人可能会为在堆栈中增加一两个额外层的成本和字典查找的成本而讨价还价,但你似乎更关心代码的美观。

另一种更简单的实现方式可能是:

def status(k=check_size) :
  select = lambda next, test : test if test else next
  d = {check_size  : lambda : select(check_color,  check_size()  ),
       check_color : lambda : select(check_tone,   check_color() ),
       check_tone  : lambda : select(check_flavor, check_tone()  ),
       check_flavor: lambda : select(None,         check_flavor())}
  while k in d : k = d[k]()
  return k

我指的不是pep8,而是用一个简明的描述性词语来代替句子。当然OP可能会遵循一些编码惯例,使用一些现有的代码库,或者不关心代码库中的简洁术语。

其他回答

python的方法是使用reduce(有人已经提到过)或itertools(如下所示),但在我看来,简单地使用或操作符的短路可以产生更清晰的代码

from itertools import imap, dropwhile

def check_all_conditions():
    conditions = (check_size,\
        check_color,\
        check_tone,\
        check_flavor)
    results_gen = dropwhile(lambda x:not x, imap(lambda check:check(), conditions))
    try:
        return results_gen.next()
    except StopIteration:
        return None

这是马丁第一个例子的一个变种。为了允许短路,它还使用了“可调用对象的集合”样式。

而不是循环,你可以使用内置的任何。

conditions = (check_size, check_color, check_tone, check_flavor)
return any(condition() for condition in conditions) 

注意,any返回一个布尔值,所以如果您需要检查的确切返回值,这个解决方案将不起作用。any将不会区分14,'red', 'sharp', 'spicy'作为返回值,它们都将作为True返回。

这种方式有点超出框框,但我认为最终结果是简单的,可读的,而且看起来不错。

基本思想是,当其中一个函数求值为真值时引发异常,并返回结果。下面是它的外观:

def check_conditions():
    try:
        assertFalsey(
            check_size,
            check_color,
            check_tone,
            check_flavor)
    except TruthyException as e:
        return e.trigger
    else:
        return None

你需要一个assertFalsey函数,当一个被调用的函数参数的值为真时,它会引发一个异常:

def assertFalsey(*funcs):
    for f in funcs:
        o = f()
        if o:
            raise TruthyException(o)

可以对上面的内容进行修改,以便为要计算的函数提供参数。

当然你需要TruthyException本身。这个异常提供了触发异常的对象:

class TruthyException(Exception):
    def __init__(self, obj, *args):
        super().__init__(*args)
        self.trigger = obj

当然,您可以将原始函数转换为更一般的函数:

def get_truthy_condition(*conditions):
    try:
        assertFalsey(*conditions)
    except TruthyException as e:
        return e.trigger
    else:
        return None

result = get_truthy_condition(check_size, check_color, check_tone, check_flavor)

这可能会慢一点,因为您同时使用if语句和处理异常。但是,该异常最多只处理一次,因此对性能的影响应该很小,除非您希望运行该检查并获得成千上万次的True值。

实际上与timgeb的答案相同,但你可以使用括号来更好地格式化:

def check_all_the_things():
    return (
        one()
        or two()
        or five()
        or three()
        or None
    )

上面的Martijns的第一个例子略有变化,避免了循环中的if:

Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
  Status = Status or c();
return Status