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()
。 请注意,如果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评估为真,并且返回结果时raise
exception。 以下是它的外观:
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可能遵循一些编码惯例,工作一个现有的代码库或不在乎他们的代码库中的简洁的条款。