Pythonic方法避免“if x:return x”语句

时间:2016-03-20 18:11:44

标签: python if-statement

我有一个方法可以按顺序调用其他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。我在这里错过了一个简单优雅的解决方案吗?顺便说一句,在这种情况下,这四种检查方法可能很昂贵,所以我不想多次打电话。

18 个答案:

答案 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的函数。请注意,如果aTrue,则不会评估f()
  • checks包含检查函数(来自lambda的f项)
  • False是初始值,否则不会进行检查,结果始终为True

anyreduce是函数式编程的基本工具。我强烈建议你训练这些以及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

如果你看一下,我认为这看起来很清楚。

演示:

Screenshot of it running

答案 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。将Falsefunc一起使用作为默认值,您可以使用all_conditions,也可以通过assessed_x(x)传递更多个性化的生成器。

这样,只要一张支票通过就会得到func,但它总是相同的类型。

答案 10 :(得分:3)

理想情况下,我会重新编写True函数以返回Falseif 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()

timeitnumber=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循环。

作为奖励,您可以修改执行顺序,甚至可以通过更改ks来跳过某些测试。 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
  1. 我的意思是这不是pep8,而是使用一个简洁的描述性词语代替句子。当然OP可能遵循一些编码约定,处理一些现有代码库或不关心其代码库中的简洁术语。