我有一个方法,它依次调用 4 个其他方法来检查特定条件,并在返回真值时立即返回(不检查以下那些)。
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。我在这里错过了一个简单、优雅的解决方案吗?顺便说一句,在这种情况下,这四种检查方法可能很昂贵,所以我不想多次调用它们。
x and return x
比 if x: return x
好?后者更具可读性,因此可维护。您不必太担心字符或行数;可读性很重要。无论如何,它们是完全相同数量的非空白字符,如果你真的必须,if x: return x
将在一行上正常工作。
x
(而不是 bool(x)
)所以我认为假设 OP 的函数可以返回任何东西是安全的,他想要第一个任何东西这是真的。
除了 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
你可以使用一个循环:
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)
any
而不是循环。 return any(condition() for condition in conditions)
any
内部具有几乎相同的实现。但它看起来好多了,请将其发布为答案)
conditions
和 arguments
的想法?恕我直言,这比原始代码差得多,原始代码需要大约 10 秒才能被我的 Brainparser 解析。
return next((check for check in checks if check), None)
。
不要改变它
正如其他各种答案所示,还有其他方法可以做到这一点。没有一个像您的原始代码那样清晰。
:
之后的换行符,因为我认为 if x: return x
非常好,它使函数看起来更紧凑。但这可能只是我。
or
是一个正确且易于理解的习语。许多语言都有这个;也许当它被称为 orelse
时,它会更清楚,但即使是普通的旧 or
(或其他语言中的 ||
)也意味着被理解为第一个尝试的替代方法一个“不起作用”。
or
,但这无论如何都是一个意见问题——应该如此。
实际上与 timgeb 的答案相同,但您可以使用括号来获得更好的格式:
def check_all_the_things():
return (
one()
or two()
or five()
or three()
or None
)
根据 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
这避免了:
复杂的逻辑结构
真的很长的线
重复
...同时保持线性、易于阅读的流程。
根据您的特定情况,您可能还可以提出更好的函数名称,使其更具可读性。
return None
不是必需的,因为函数默认返回 None
。但是,明确返回 None
并没有错,我喜欢您选择这样做。
这是 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
返回。
next(itertools.ifilter(None, (c() for c in conditions)))
来获取实际值,而无需将其转换为布尔值。
any
真的短路了吗?
x = bar(); if x: return x;
之后我的眼睛变得呆滞
您是否考虑过将 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()
])
请注意,尽管此实现可能是最清晰的,但它会评估所有检查,即使第一个检查是 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 是迭代的更新值。如果存在可选初始化器,则将其放置在计算中的可迭代项之前
在你的情况下:
lambda a, f: a 或 f() 是检查累加器 a 或当前检查 f() 是否为 True 的函数。请注意,如果 a 为 True,则不会计算 f()。
checks 包含检查函数(来自 lambda 的 f 项)
False 是初始值,否则不会发生检查,结果将始终为 True
any
和 reduce
是函数式编程的基本工具。我强烈建议您训练这些以及map
,这也很棒!
any
仅在检查实际返回布尔值(字面意思是 True
或 False
)时才有效,但问题没有具体说明。您需要使用 reduce
来返回支票返回的实际值。此外,使用生成器(例如 any(c() for c in (check_size, check_color, check_tone, check_flavor))
)很容易避免使用 any
评估所有检查。如 Leonhard's answer
reduce
的解释和用法。与@DavidZ 一样,我相信您使用 any
的解决方案应该使用生成器,并且需要指出它仅限于返回 True
或 False
。
any
使用真实值:any([1, "abc", False]) == True
和 any(["", 0]) == False
any
仅适用于 目的。
如果你想要相同的代码结构,你可以使用三元语句!
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
如果你看一下,我认为这看起来很好而且很清楚。
演示:
https://i.stack.imgur.com/gYcy8.png
x if x else <something>
可以简化为 x or <something>
对我来说,最好的答案是来自@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
进行检查,并返回 True
或 False
。通过使用 func
和 all_conditions
作为默认值,您可以使用 assessed_x(x)
,或者您可以通过 func
传递进一步的个性化生成器。
这样,只要一张支票通过,您就会得到 x
,但它始终是相同的类型。
理想情况下,我会重写 check_
函数以返回 True
或 False
而不是一个值。你的支票然后变成
if check_size(x):
return x
#etc
假设您的 x
不是不可变的,您的函数仍然可以修改它(尽管他们不能重新分配它) - 但是一个名为 check
的函数无论如何都不应该真正修改它。
我喜欢@timgeb 的。与此同时,我想补充一点,不需要在 return
语句中表达 None
,因为会评估 or
分隔语句的集合,并返回第一个非零、非空、非空,并且如果没有,则无论是否有 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
结合使用,我查看了一些建议的运行时间。为了比较,我只是使用 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
上面 Martijns 的第一个示例稍有变化,避免了循环内的 if:
Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
Status = Status or c();
return Status
Status
为真,Status or c()
将跳过/短路评估对 c()
的调用,因此此答案中的代码似乎不会调用比 OP 的代码更多的函数。 stackoverflow.com/questions/2580136/…
这种方式有点开箱即用,但我认为最终结果简单、易读且看起来不错。
基本思想是 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
函数,当调用的函数参数之一评估为真时引发异常:
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
值。
StopIteration
是一个很好的例子:每次用完一个可迭代对象时都会引发一个异常。您要避免的事情是一遍又一遍地连续引发异常,这会变得昂贵。但是做一次就不行了。
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
如果您需要 Python 3.8,您可以使用“assignment expressions”的新功能来减少 if-else 链的重复性:
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
if ( x := check_size() ) :
以获得相同的效果。
或使用 max
:
def check_all_conditions():
return max(check_size(), check_color(), check_tone(), check_flavor()) or None
过去,我已经看到了一些有趣的带有 dicts 的 switch/case 语句的实现,这让我得到了这个答案。使用您提供的示例,您将获得以下信息。 (这是疯狂的 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 循环。
作为奖励,您可以修改执行顺序,甚至通过更改 k
和 s
来跳过一些测试,例如 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 可能遵循一些编码约定,使用一些现有的代码库或不关心其代码库中的简洁术语。
check_no/some/even/prime/every_third/fancy_conditions
的函数,而只有这一个函数,所以为什么不将其称为 status
或者如果坚持使用 check_status
。使用 _all_
是多余的,他不能确保宇宙的完整性。命名肯定应该使用一组一致的关键字,尽可能利用名称间距。冗长的句子最好用作文档字符串。一个人很少需要超过 8-10 个字符来简洁地描述某事。
check_all_conditions
是一个坏名字,因为它不检查所有条件是否为真。我会使用 matches_any_condition
之类的东西。
\
将每张支票放在自己的行上。\
来扩展逻辑线。尽可能使用括号;所以return (....)
根据需要插入换行符。不过,这将是一条很长的逻辑线。