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()是否为Truef()。 请注意,如果a为True,f()将不被评估。 -
checks包含检查函数(lambda的f项) -
False是初始值,否则不会检查,结果总是为True
any和reduce都是函数式编程的基本工具。 我强烈地鼓励你训练这些以及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 ,并返回True或False 。 通过使用all_conditions作为默认值的func ,您可以使用all_conditions assessed_x(x) ,或者您可以通过func传递更多的个性all_conditions 。
这样,只要一张支票通过,你就可以得到x ,但总是是相同的types。
理想情况下,我会重新写入check_函数返回True或False而不是一个值。 你的支票就变成了
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()
使用timeit和number=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循环。
作为奖励,你可以修改执行顺序,甚至通过改变k和s来跳过一些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
- 我的意思不是用pep8来表示,而是用一个简洁的描述词代替句子。 授予OP可能遵循一些编码惯例,工作一个现有的代码库或不在乎他们的代码库中的简洁的条款。