首页 文章

Pythonic方法避免“if x:return x”语句

提问于
浏览
212

我有一个方法,按顺序调用其他4个方法来检查特定条件,并在每次返回Truthy时立即返回(不检查以下方法) .

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

这似乎是很多 Baggage 代码 . 而不是每个2行if语句,我宁愿做类似的事情:

x and return x

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

17 回答

  • 271

    我要跳到这里并且从未写过一行Python,但我认为 if x = check_something(): return x 有效吗?

    如果是这样:

    def check_all_conditions():
    
        if (x := check_size()): return x
        if (x := check_color()): return x
        if (x := check_tone()): return x
        if (x := check_flavor()): return x
    
        return None
    
  • 3

    你有没有考虑过只在一行写 if x: return x

    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
    

    这并不比你的重复少,但IMNSHO它看起来相当平滑 .

  • 1

    你可以使用一个循环:

    conditions = (check_size, check_color, check_tone, check_flavor)
    for condition in conditions:
        result = condition()
        if result:
            return result
    

    这具有额外的优势,您现在可以使条件数变量 .

    你可以使用map() filter()(Python 3版本,在Python 2中使用future_builtins versions)来获得第一个这样的匹配值:

    try:
        # Python 2
        from future_builtins import map, filter
    except ImportError:
        # Python 3
        pass
    
    conditions = (check_size, check_color, check_tone, check_flavor)
    return next(filter(None, map(lambda f: f(), conditions)), None)
    

    但如果这更具可读性是值得商榷的 .

    另一种选择是使用生成器表达式:

    conditions = (check_size, check_color, check_tone, check_flavor)
    checks = (condition() for condition in conditions)
    return next((check for check in checks if check), None)
    
  • 83

    Don't change it

    正如各种其他答案所示,还有其他方法可以做到这一点 . 没有一个像你的原始代码一样清晰 .

  • 72

    理想情况下,我会重写 check_ 函数以返回 TrueFalse 而不是值 . 然后你的支票变成了

    if check_size(x):
        return x
    #etc
    

    假设你的 x 不是不可变的,你的函数仍然可以修改它(虽然它们不能重新分配它) - 但是一个名为 check 的函数无论如何都不应该真正修改它 .

  • 0

    我已经看到过去使用dicts的一些有趣的switch / case语句实现让我得到了这个答案 . 使用这个例子你've provided you would get the following. (It' s madness using_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() if check_FUNCTION() else next . 这对于长时间运行的功能很有用 . dict中的lambdas延迟执行它的值直到while循环 .

    作为奖励,您可以修改执行顺序,甚至可以通过更改 ks 来跳过某些测试 . k='c',s={'c':'b','b':None} 减少了测试次数并反转了原始处理顺序 .

    timeit 研究员可能讨厌在堆栈中添加额外的一层或两层的成本,并且dict的成本会有所提高,但你似乎更关心代码的漂亮 .

    或者,更简单的实现可能如下:

    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可能遵循一些编码约定,处理一些现有代码库或不关心其代码库中的简洁术语 .
  • 2

    根据Curly's law,您可以通过分割两个问题来使此代码更具可读性:

    • 我要检查什么?

    • 有一件事是真的吗?

    分为两个功能:

    def all_conditions():
        yield check_size()
        yield check_color()
        yield check_tone()
        yield check_flavor()
    
    def check_all_conditions():
        for condition in all_conditions():
            if condition:
                return condition
        return None
    

    这避免了:

    • 复杂的逻辑结构

    • 真的很长

    • 重复

    ...同时保留线性,易读的流程 .

    根据您的特定情况,您可能还可以提供更好的功能名称,这使其更具可读性 .

  • 18

    这是Martijns第一个例子的变种 . 它还使用“callables”样式,以便允许短路 .

    您可以使用内置 any 而不是循环 .

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

    请注意 any 返回一个布尔值,因此如果您需要检查的确切返回值,此解决方案将不起作用 . any 将不区分 14'red''sharp''spicy' 作为返回值,它们将全部作为 True 返回 .

  • 41

    我很惊讶没有人提到为此目的而制作的内置any

    def check_all_conditions():
        return any([
            check_size(),
            check_color(),
            check_tone(),
            check_flavor()
        ])
    

    请注意,尽管此实现可能是最清楚的,但它会评估所有检查,即使第一个检查是 True .


    如果您确实需要在第一次失败检查时停止,请考虑使用reduce来将列表转换为简单值:

    def check_all_conditions():
        checks = [check_size, check_color, check_tone, check_flavor]
        return reduce(lambda a, f: a or f(), checks, False)
    

    reduce(function,iterable [,initializer]):从左到右累加两个参数的函数到iterable项,以便将iterable减少为单个值 . 左参数x是累加值,右参数y是迭代的更新值 . 如果存在可选的初始化程序,则将其放在计算中的iterable项之前

    在你的情况下:

    • lambda a, f: a or f() 是检查累加器 a 或当前检查 f()True 的函数 . 请注意,如果 aTrue ,则不会评估 f() .

    • checks 包含检查功能( f 来自lambda的项目)

    • False 是初始值,否则不会进行检查,结果将始终为 True

    anyreduce 是函数式编程的基本工具 . 我强烈建议你训练这些以及map这也很棒!

  • 386

    对我来说,最好的答案是来自@ phil-frost,然后是@ wayne-werner .

    我觉得有趣的是,没有人说过一个函数将返回许多不同数据类型的事实,这将使得必须对x本身的类型进行检查以进行任何进一步的工作 .

    所以我会将@ PhilFrost的回复与保持单一类型的想法混合在一起:

    def all_conditions(x):
        yield check_size(x)
        yield check_color(x)
        yield check_tone(x)
        yield check_flavor(x)
    
    def assessed_x(x,func=all_conditions):
        for condition in func(x):
            if condition:
                return x
        return None
    

    请注意 x 作为参数传递,但 all_conditions 用作检查函数的传递生成器,其中所有函数都检查 x ,并返回 TrueFalse . 通过使用 funcall_conditions 作为默认值,您可以使用 assessed_x(x) ,或者您可以通过 func 传递更多个性化生成器 .

    这样,一旦检查通过,你就会得到 x ,但它总是相同的类型 .

  • 78

    在与timgeb有效的答案中,您可以使用括号进行更好的格式化:

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

    pythonic方式是使用reduce(如已提到的人)或itertools(如下所示),但 it seems to me that simply using short circuiting of the or operator produces clearer code

    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
    
  • 3

    这种方式有点偏离框,但我认为最终结果是简单,可读,并且看起来不错 .

    当其中一个函数评估为真值时,基本思想是 raise 异常,并返回结果 . 以下是它的外观:

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

    你需要一个 assertFalsey 函数,当其中一个被调用的函数参数计算为truthy时会引发异常:

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

    可以修改上述内容,以便为要评估的函数提供参数 .

    当然,你需要 TruthyException 本身 . 此异常提供触发异常的 object

    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 值数千次 .

  • 1

    除了Martijn的好答案,你可以链 or . 这将返回第一个truthy值,如果没有真值,则返回 None

    def check_all_conditions():
        return check_size() or check_color() or check_tone() or check_flavor() or None
    

    演示:

    >>> x = [] or 0 or {} or -1 or None
    >>> x
    -1
    >>> x = [] or 0 or {} or '' or None
    >>> x is None
    True
    
  • 23

    如果你想要相同的代码结构,你可以使用三元语句!

    def check_all_conditions():
        x = check_size()
        x = x if x else check_color()
        x = x if x else check_tone()
        x = x if x else check_flavor()
    
        return x if x else None
    

    如果你看一下,我认为这看起来很漂亮 .

    演示:

    Screenshot of it running

  • 4

    我喜欢@ timgeb's . 在此期间,我想补充说,不需要在 return 语句中表达 None ,因为会计算 or 分离语句的集合,并返回第一个非零,非空,无 - 无,如果没有,然后 None 返回是否有 None

    所以我的 check_all_conditions() 函数看起来像这样:

    def check_all_conditions():
        return check_size() or check_color() or check_tone() or check_flavor()
    

    使用 timeitnumber=10**7 我查看了一些建议的运行时间 . 为了便于比较,我只使用 random.random() 函数返回一个字符串或 None 基于随机数 . 这是整个代码:

    import random
    import timeit
    
    def check_size():
        if random.random() < 0.25: return "BIG"
    
    def check_color():
        if random.random() < 0.25: return "RED"
    
    def check_tone():
        if random.random() < 0.25: return "SOFT"
    
    def check_flavor():
        if random.random() < 0.25: return "SWEET"
    
    def check_all_conditions_Bernard():
        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
    
    def check_all_Martijn_Pieters():
        conditions = (check_size, check_color, check_tone, check_flavor)
        for condition in conditions:
            result = condition()
            if result:
                return result
    
    def check_all_conditions_timgeb():
        return check_size() or check_color() or check_tone() or check_flavor() or None
    
    def check_all_conditions_Reza():
        return check_size() or check_color() or check_tone() or check_flavor()
    
    def check_all_conditions_Phinet():
        x = check_size()
        x = x if x else check_color()
        x = x if x else check_tone()
        x = x if x else check_flavor()
    
        return x if x else None
    
    def all_conditions():
        yield check_size()
        yield check_color()
        yield check_tone()
        yield check_flavor()
    
    def check_all_conditions_Phil_Frost():
        for condition in all_conditions():
            if condition:
                return condition
    
    def main():
        num = 10000000
        random.seed(20)
        print("Bernard:", timeit.timeit('check_all_conditions_Bernard()', 'from __main__ import check_all_conditions_Bernard', number=num))
        random.seed(20)
        print("Martijn Pieters:", timeit.timeit('check_all_Martijn_Pieters()', 'from __main__ import check_all_Martijn_Pieters', number=num))
        random.seed(20)
        print("timgeb:", timeit.timeit('check_all_conditions_timgeb()', 'from __main__ import check_all_conditions_timgeb', number=num))
        random.seed(20)
        print("Reza:", timeit.timeit('check_all_conditions_Reza()', 'from __main__ import check_all_conditions_Reza', number=num))
        random.seed(20)
        print("Phinet:", timeit.timeit('check_all_conditions_Phinet()', 'from __main__ import check_all_conditions_Phinet', number=num))
        random.seed(20)
        print("Phil Frost:", timeit.timeit('check_all_conditions_Phil_Frost()', 'from __main__ import check_all_conditions_Phil_Frost', number=num))
    
    if __name__ == '__main__':
        main()
    

    以下是结果:

    Bernard: 7.398444877040768
    Martijn Pieters: 8.506569201346597
    timgeb: 7.244275416364456
    Reza: 6.982133448743038
    Phinet: 7.925932800076634
    Phil Frost: 11.924794811353031
    
  • -2

    Martijns上面的第一个例子略有不同,它避免了循环内部的if:

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

相关问题