我有一个方法可以按顺序调用其他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。我在这里错过了一个简单优雅的解决方案吗?顺便说一句,在这种情况下,这四种检查方法可能很昂贵,所以我不想多次打电话。
答案 0 :(得分:391)
除了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
答案 1 :(得分:275)
您可以使用循环:
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)
答案 2 :(得分:84)
不要改变
正如其他各种答案所示,还有其他方法可以做到这一点。没有一个像原始代码一样清晰。
答案 3 :(得分:81)
与timgeb实际上有相同的答案,但您可以使用括号进行更好的格式化:
def check_all_the_things():
return (
one()
or two()
or five()
or three()
or None
)
答案 4 :(得分:73)
根据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
这可以避免:
...同时保留线性,易读的流程。
根据您的具体情况,您可能还可以提供更好的功能名称,这使其更具可读性。
答案 5 :(得分:41)
这是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
。
答案 6 :(得分:26)
您是否考虑过将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它读得更顺畅。
答案 7 :(得分:23)
我很惊讶没有人提到为此目的而制作的内置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])
:应用两个函数 参数累加到可迭代的项目,从左到右, 以便将迭代减少到单个值。左参数x, 是累计值,右边的参数y是更新 来自可迭代的值。如果存在可选的初始化程序,则为 放在计算中的iterable项之前
在你的情况下:
lambda a, f: a or f()
是检查累加器a
或当前支票f()
是True
的函数。请注意,如果a
为True
,则不会评估f()
。checks
包含检查函数(来自lambda的f
项)False
是初始值,否则不会进行检查,结果始终为True
any
和reduce
是函数式编程的基本工具。我强烈建议你训练这些以及map
这也很棒!
答案 8 :(得分:18)
如果你想要相同的代码结构,你可以使用三元语句!
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
如果你看一下,我认为这看起来很清楚。
演示:
答案 9 :(得分:4)
对我来说,最好的答案是来自@ phil-frost,然后是@ wayne-werner's。
我觉得有趣的是,没有人说过一个函数将返回许多不同数据类型的事实,这将使得必须对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
,并返回{ {1}}或True
。将False
与func
一起使用作为默认值,您可以使用all_conditions
,也可以通过assessed_x(x)
传递更多个性化的生成器。
这样,只要一张支票通过就会得到func
,但它总是相同的类型。
答案 10 :(得分:3)
理想情况下,我会重新编写True
函数以返回False
或if check_size(x):
return x
#etc
而不是值。然后你的支票就变成了
x
假设你的check
不是不可变的,你的函数仍然可以修改它(虽然它们不能重新分配) - 但是一个名为function drawComplicatedThing(ctx) {
let [px, py] = [0, 0];
for (let i = 0; i < 10000; i++) {
let {x, y} = computeExpensive(i);
// black along
ctx.beginPath();
ctx.moveTo(px, py);
ctx.lineTo(x, y);
ctx.strokeStyle = 'black';
ctx.stroke();
// red transpose
ctx.beginPath();
ctx.moveTo(py, px);
ctx.lineTo(y, x);
ctx.strokeStyle = 'red';
ctx.stroke();
[px, py] = [x, y];
}
}
的函数不应该真正修改它。
答案 11 :(得分:3)
Martijns上面的第一个例子略有不同,它避免了循环中的if:
Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
Status = Status or c();
return Status
答案 12 :(得分:2)
这种方式有点偏离框,但我认为最终结果是简单,可读,并且看起来不错。
基本思想是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
值数千次。
答案 13 :(得分:1)
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
答案 14 :(得分:1)
我喜欢@ timgeb's。与此同时,我想补充一点,None
语句中不需要表达return
,因为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
一起使用我查看了一些建议的运行时间。为了便于比较,我只使用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
答案 15 :(得分:0)
我要跳到这里并且从未写过一行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
答案 16 :(得分:0)
或使用max
:
def check_all_conditions():
return max(check_size(), check_color(), check_tone(), check_flavor()) or None
答案 17 :(得分:-2)
我已经看到过去使用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