Pythonic way to avoid “if x: return x” statements
我有一个方法,按顺序调用其他4个方法来检查特定的条件,并在每次返回Truthy时立即返回(不检查以下的方法)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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语句,我宁愿做类似的事情:
1 | x and return x |
但那是无效的Python。 我在这里错过了一个简单优雅的解决方案吗? 顺便说一句,在这种情况下,这四种检查方法可能很昂贵,所以我不想多次调用它们。
除了Martijn的好答案,你可以链接
1 2 | def check_all_conditions(): return check_size() or check_color() or check_tone() or check_flavor() or None |
演示:
1 2 3 4 5 6 | >>> x = [] or 0 or {} or -1 or None >>> x -1 >>> x = [] or 0 or {} or '' or None >>> x is None True |
你可以使用一个循环:
1 2 3 4 5 | conditions = (check_size, check_color, check_tone, check_flavor) for condition in conditions: result = condition() if result: return result |
这具有额外的优势,您现在可以使条件数变量。
您可以使用
1 2 3 4 5 6 7 8 9 | 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) |
但如果这更具可读性是值得商榷的。
另一种选择是使用生成器表达式:
1 2 3 | 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) |
不要改变它
正如各种其他答案所示,还有其他方法可以做到这一点。没有一个像你的原始代码一样清晰。
在与timgeb有效的答案中,您可以使用括号进行更好的格式化:
1 2 3 4 5 6 7 8 | def check_all_the_things(): return ( one() or two() or five() or three() or None ) |
根据Curly定律,您可以通过分割两个问题来使这些代码更具可读性:
- 我要检查什么?
- 有一件事是真的吗?
分为两个功能:
1 2 3 4 5 6 7 8 9 10 11 | 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 |
这避免了:
- 复杂的逻辑结构
- 真的很长
- 重复
...同时保留线性,易读的流程。
根据您的特定情况,您可能还可以提供更好的功能名称,这使其更具可读性。
这是Martijns第一个例子的变种。它还使用"callables"风格,以便允许短路。
您可以使用builtin
1 2 | conditions = (check_size, check_color, check_tone, check_flavor) return any(condition() for condition in conditions) |
请注意,
您是否考虑过只在一行写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 2 3 4 5 6 7 | def check_all_conditions(): return any([ check_size(), check_color(), check_tone(), check_flavor() ]) |
请注意,尽管此实现可能是最清楚的,但它会评估所有检查,即使第一个检查是
如果您确实需要在第一次失败检查时停止,请考虑使用
1 2 3 | 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]) : Apply function of two
arguments cumulatively to the items of iterable, from left to right,
so as to reduce the iterable to a single value. The left argument, x,
is the accumulated value and the right argument, y, is the update
value from the iterable. If the optional initializer is present, it is
placed before the items of the iterable in the calculation
在你的情况下:
-
lambda a, f: a or f() 是检查累加器a 或当前检查f() 是否为True 的函数。请注意,如果a True ,则不会评估f() 。 -
checks 包含检查函数(来自lambda的f 项) -
False 是初始值,否则不会进行检查,结果将始终为True
如果你想要相同的代码结构,你可以使用三元语句!
1 2 3 4 5 6 7 | 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 |
如果你看一下,我觉得这很好看。
演示:
对我来说,最好的答案是来自@ phil-frost,然后是@ wayne-werner's。
我觉得有趣的是,没有人说过一个函数将返回许多不同数据类型的事实,这将使得必须对x本身的类型进行检查以进行任何进一步的工作。
所以我会将@ PhilFrost的回复与保持单一类型的想法混合在一起:
1 2 3 4 5 6 7 8 9 10 11 | 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 |
请注意,
这样,一旦检查通过,你就会得到
理想情况下,我会重写
1 2 3 | if check_size(x): return x #etc |
假设你的
Martijns上面的第一个例子略有不同,它避免了循环内部的if:
1 2 3 4 | Status = None for c in [check_size, check_color, check_tone, check_flavor]: Status = Status or c(); return Status |
这种方式有点偏离框,但我认为最终结果是简单,可读,并且看起来不错。
当其中一个函数计算为真值时,基本思想是
1 2 3 4 5 6 7 8 9 10 11 | def check_conditions(): try: assertFalsey( check_size, check_color, check_tone, check_flavor) except TruthyException as e: return e.trigger else: return None |
你需要一个
1 2 3 4 5 | def assertFalsey(*funcs): for f in funcs: o = f() if o: raise TruthyException(o) |
可以修改上述内容,以便为要评估的函数提供参数。
当然,你需要
1 2 3 4 | class TruthyException(Exception): def __init__(self, obj, *args): super().__init__(*args) self.trigger = obj |
您可以将原始功能转换为更通用的功能,当然:
1 2 3 4 5 6 7 8 9 | 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) |
这可能会慢一些,因为您同时使用
pythonic方式是使用reduce(如已经提到的那样)或itertools(如下所示),但在我看来,简单地使用
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
我喜欢@ timgeb's。 与此同时,我想补充说,不需要在
所以我的
1 2 | def check_all_conditions(): return check_size() or check_color() or check_tone() or check_flavor() |
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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() |
以下是结果:
1 2 3 4 5 6 | Bernard: 7.398444877040768 Martijn Pieters: 8.506569201346597 timgeb: 7.244275416364456 Reza: 6.982133448743038 Phinet: 7.925932800076634 Phil Frost: 11.924794811353031 |
或者使用
1 2 | def check_all_conditions(): return max(check_size(), check_color(), check_tone(), check_flavor()) or None |
我要跳到这里并且从未写过一行Python,但我认为
如果是这样:
1 2 3 4 5 6 7 8 | 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 |
我在过去看过一些有关switchs / case语句的有趣实现,这使我得到了这个答案。使用您提供的示例,您将获得以下内容。 (这是疯狂
1 2 3 4 5 6 7 8 | 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函数无需调用每个
作为奖励,您可以修改执行顺序,甚至可以通过更改
或者,更简单的实现可能如下:
1 2 3 4 5 6 7 8 | 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 |