Pythonic避免“如果x:return x”语句的方式

我有一个方法,依次调用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 

这看起来像很多行李代码。 而不是每条2行if语句,我宁愿做一些事情:

 x and return x 

但是这是无效的Python。 我错过了一个简单,优雅的解决scheme吗? 顺便说一下,在这种情况下,这四种检查方法可能是昂贵的,所以我不想多次打电话给他们。

你可以使用循环:

 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版本 )来获取第一个匹配值:

 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) 

但如果这是更可读的是值得商榷的。

另一种select是使用生成器expression式:

 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) 

或者对Martijn的正确答案,你可以连锁or 。 这将返回第一个真值,或者如果没有真值,则返回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 

不要改变它

还有其他的方法可以做到这一点,就像其他的答案一样。 没有比你原来的代码清晰。

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

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

根据Curly定律 ,通过分解两个问题,可以使代码更具可读性:

  • 我检查什么东西?
  • 有一件事情回复真实?

分为两个function:

 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 

这避免了:

  • 复杂的逻辑结构
  • 真的很长
  • 重复

…同时保留线性,易于阅读的stream程。

根据你的具体情况,你也可以想出更好的函数名,这使得它更具可读性。

这是Martijns第一个例子的一个变种。 它也使用“可收集的可收回的”样式来允许短路。

而不是一个循环,你可以使用any内置。

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

请注意, any返回一个布尔值,所以如果你需要支票的确切返回值,这个解决scheme将不起作用。 any不区分14'red''sharp''spicy'作为返回值,他们都将被返回为True

你有没有考虑过写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它读得相当顺畅。

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

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

请注意,尽pipe这个实现可能是最清晰的,但是即使第一个是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]) :将两个参数的函数累积地应用到可迭代项,从左到右,从而将迭代次数减less到单个值。 左边的参数x是累加的值,右边的参数是来自可迭代的更新值。 如果可选初始化程序存在,则将其放置在计算中可迭代项的前面

在你的情况下:

  • lambda a, f: a or f()是检查累加器a或当前检查f()是否为True f() 。 请注意,如果aTruef()将不被评估。
  • checks包含检查函数(lambda的f项)
  • False是初始值,否则不会检查,结果总是为True

anyreduce都是函数式编程的基本工具。 我强烈地鼓励你训练这些以及map哪些是真棒!

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

 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 

我认为这看起来很好,很清楚。

演示:

它运行的屏幕截图

上面Martijns第一个例子的一个小的变化,这避免了如果在循环内:

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

对我来说,最好的答案是从@ phil-frost,然后是@ wayne-werner's。

我觉得有趣的是,没有人说过一个函数会返回许多不同的数据types,这将使​​得强制检查x本身的types来做进一步的工作。

所以我将@ PhilFrost的回应与保持单一types的想法相混合:

 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是作为parameter passing的,但是all_conditions也被用作检查函数的传递生成器,其中所有这些函数都得到要检查的x ,并返回TrueFalse 。 通过使用all_conditions作为默认值的func ,您可以使用all_conditions assessed_x(x) ,或者您可以通过func传递更多的个性all_conditions

这样,只要一张支票通过,你就可以得到x ,但总是是相同的types。

理想情况下,我会重新写入check_函数返回TrueFalse而不是一个值。 你的支票就变成了

 if check_size(x): return x #etc 

假设你的x不是不可变的,你的函数仍然可以修改它(虽然它们不能重新分配它) – 但是一个名为check的函数不应该真的在修改它。

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

基本的想法是当一个function评估为真,并且返回结果时raiseexception。 以下是它的外观:

 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时引发一个exception:

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

上述内容可以进行修改,以便为要评估的function提供参数。

当然,你需要TruthyException本身。 这个exception提供了触发exception的object

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

当然,你可以把原来的function变成更一般的东西:

 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语句和处理exception。 但是,例外情况最多只能处理一次,所以对性能的打击应该是微不足道的,除非您希望运行检查并获得成千上万次的True值。

pythonic的方式是使用reduce(正如已经提到的人)或itertools(如下所示),但在我看来,简单地使用or运算符的短路产生更清晰的代码

 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的。 同时,我想补充一点,在return语句中expressionNone并不是必须的,因为集合or独立的语句被评估,并且返回第一个非零,非空,none-None,如果没有那么无论是否有None返回None

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

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

使用timeitnumber=10**7我查看了一些build议的运行时间。 为了比较,我只是使用random.random()函数返回一个string或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 

我要跳到这里,从来没有写过一行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 

在过去,我已经看到了一些开关/案例语句的有趣的实现,并带有我的答案。 使用你提供的例子会得到以下结果。 (这是疯狂的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通过添加另一个函数层, check_FUNCTION() if check_FUNCTION() else next避免了check_FUNCTION() if check_FUNCTION() else next 。 这对于长时间运行的function很有用。 字典中的lambdaexpression式延迟执行它的值,直到while循环。

作为奖励,你可以修改执行顺序,甚至通过改变ks来跳过一些testing,例如k='c',s={'c':'b','b':None}减lesstesting的次数并颠倒原来的处理顺序。

timeit研究员可能会讨价还价,增加额外的一到两层的费用和字典的查找成本,但你似乎更关心代码的漂亮。

另外一个更简单的实现可能如下:

 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 
  1. 我的意思不是用pep8来表示,而是用一个简洁的描述词代替句子。 授予OP可能遵循一些编码惯例,工作一个现有的代码库或不在乎他们的代码库中的简洁的条款。