【问题标题】:Nested if's - what's more Pythonic?嵌套 if's - 什么是 Pythonic?
【发布时间】:2016-06-03 08:25:24
【问题描述】:

这两个函数做同样的事情。

def function1(self):
    a = self.get_a()
    b = self.get_b()
    c = self.get_c()
    r = None

    if a:
        r = a
        if b:
            r = b
            if c:
                r = c
            else:
                print("c not set.")
        else:
            print("b not set.")
    else:
        print("a not set.")

    return r



def function2(self):
    a = self.get_a()
    b = self.get_b()
    c = self.get_c()
    r = None

    if not a:
        print("a not set.")
        return r

    r = a
    if not b:
        print("b not set.")
        return r

    r = b
    if not c:
        print("c not set.")

    r = c
    return r

function1() 创建的行越多,嵌套的 if 越多,这与 PEP8 的行长限制 78 冲突。

function2() 可能更难阅读/理解并且有更多的返回语句。行长在这里没有问题。

哪个更pythonic?

【问题讨论】:

  • 也可以去掉r,直接返回abc
  • 如果您不需要print 分支中的print 调用,您可以使用a and (b and c or b) or a or None 一次性完成整个计算。最后的or None 仅在您要测试的潜在“虚假”值不限于无(并且您关心实际返回None 而不是其他一些虚假的东西)时才需要。
  • @Blckknght 我实际上在提交之前没有看到您的评论,如果您想提交它作为答案,我将删除我的。
  • 好吧,实际上,如果 a 和 b 为真,但 c 为假,这些函数的行为会有所不同;-) Cf。我对比较的回答以及您可能使用的一些模式符合 PEP8 并具有可读、明确和可扩展的实现。祝我们大家学习 Python 愉快...

标签: python if-statement nested indentation pep


【解决方案1】:

Pythonic 代码的原则之一是“平面优于嵌套”。在此基础上,我会说function2() 客观上更加 Pythonic。这可以在PEP-20: The Zen of Python看到:

Python 之禅

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

这可以通过在 Python 解释器中输入 import this 来查看。

【讨论】:

  • 感谢import this。我真的没见过这个
【解决方案2】:

正如@Will 的回答所暗示的,平坦更好。但是无论如何,代码看起来并不是很漂亮。更紧凑的代码类型怎么样?

从@Will 的回答中查看这些引语:

可读性很重要。

美胜于丑。

from collections import OrderedDict
def function3():
    my_dictionary=OrderedDict()
    my_dictionary['a'] = self.get_a()
    my_dictionary['b'] = self.get_b()
    my_dictionary['c'] = self.get_c()
    # ...
    r = None

    for name in my_dictionary.keys():
        value = my_dictionary[name]
        if not value:
            print("%s not set." % name)
            return r
        r = value
    return r

这当然可以进一步改进

【讨论】:

  • 但是 function_1、function_2 和 function_3 的行为都是成对不同的 ;-) cf。我提出的答案的测试结果
  • 哦,忘了最后一个return 来匹配funct1。反正就是原理问题
【解决方案3】:

您可以使用andor 运算符的求值规则,例如:

>>> None or 4 or None or 5
4

>>> 4 and 5
5

所以你会有类似的东西:

def function3(self):
    a = self.get_a()
    b = self.get_b()
    c = self.get_c()
    return (a and b and c) or (a and b) or a or None

我建议你从逻辑代码中分解出 I/O。

【讨论】:

  • 可读性和可扩展性很重要——如果它永远保持 3 个请求维度和一个默认值,那么您建议的组合逻辑在线工具将是我的最爱,但在过去的几十年里,在我的星球上几乎总是需要扩展维度,例如另一个传感器、另一个属性,这表明一个人迟早要声明一个订单,挑选匹配的容器,然后遍历这些......
  • @Dilettant 我认为通过一些生成器表达式的魔法将其扩展到一般情况会非常简单。喜欢:reduce(lambda a, b: a or b, [reduce(lambda x, y: x and y, vals[:i]) for i in range(1,len(vals)+1)], None) 其中vals 包含您感兴趣的值列表。或者类似的东西我还没有深入考虑过。
  • 有一个原因,为什么 reduce 从 Python3 的内置函数中消失了 ;-) 在本页给出的示例的上下文中,所有这些都声称在功能上是等效的,但大多数情况下并非如此,很明显,复杂的(深度嵌套的function_1),复制粘贴(function_2),本质上是单行(你的建议)是多么容易比较,确保平等并且在维护中都有额外的重量......我理解OP成为掌握 Python 之旅的开端。
  • @Dilettant 我承认你的观点是正确的。公平地说,你有点支持我进入一个过早重构的角落......我只用了一个单行 reduce 表达式,因为我想出一些关于 cmets 部分的想法。我对这个答案的主要观点是,通常可以在不诉诸明确条件的情况下处理您的逻辑。我试图强调的是,几乎任何程序都有一个功能核心,只需使用表达式就可以优雅地表达(通常在 python 中,itertools 的健康剂量)。
【解决方案4】:

我建议下面显示的 function_4 以及问题(非相同工作!)函数和 DomTomCat 的答案之一:

#! /usr/bin/env python
from __future__ import print_function
from collections import OrderedDict  # Only used in function_3


def function_4(self):
    """Iterate over call results in FIFO on False or if sequence
    exhausted, return None or previous value if that evaluates to true."""

    functors = (
        self.get_a,
        self.get_b,
        self.get_c,
    )
    request_targets = (
        'a',
        'b',
        'c',
    )
    response_value = None
    for functor, request_target in zip(functors, request_targets):
        current_response = functor()
        if not current_response:
            print(request_target, "not set.")
            return response_value
        else:
            response_value = current_response

    return response_value


class Foo(object):
    """Mock the thingy ..."""
    def __init__(self, a, b, c):
        self._a, self._b, self._c = a, b, c

    def __repr__(self):
        return (
            "Foo(" + str(self._a) + ", " + str(self._b) + ", " +
            str(self._c) + ")")

    def get_a(self):
        return self._a

    def get_b(self):
        return self._b

    def get_c(self):
        return self._c


def function_1(self):
    a = self.get_a()
    b = self.get_b()
    c = self.get_c()
    r = None

    if a:
        r = a
        if b:
            r = b
            if c:
                r = c
            else:
                print("c not set.")
        else:
            print("b not set.")
    else:
        print("a not set.")

    return r


def function_2(self):
    a = self.get_a()
    b = self.get_b()
    c = self.get_c()
    r = None

    if not a:
        print("a not set.")
        return r

    r = a
    if not b:
        print("b not set.")
        return r

    r = b
    if not c:
        print("c not set.")

    r = c
    return r


def function_3(self):
    my_dictionary = OrderedDict()
    my_dictionary['a'] = self.get_a()
    my_dictionary['b'] = self.get_b()
    my_dictionary['c'] = self.get_c()
    # ...
    r = None

    for name in my_dictionary.keys():
        value = my_dictionary[name]
        if not value:
            print("%s not set." % name)
            return r
        r = value


def main():
    """"Drive the investigation."""
    fixtures = (
        (1, 42, 3.1415),
        (0, 42, 3.1415),
        (1, 0, 3.1415),
        (1, 42, 0),
    )
    functors = (
        function_1,
        function_2,
        function_3,
        function_4,
    )
    for fixture in fixtures:
        foo = Foo(*fixture)
        print("\nFixture:", foo)
        for i, functor in enumerate(functors, start=1):
            print("Functor[%d]:" % (i,))
            print(functor(foo))


if __name__ == '__main__':
    main()

在我的机器上,灯具在被调用时会产生以下行为/输出:

Fixture: Foo(1, 42, 3.1415)
Functor[1]:
3.1415
Functor[2]:
3.1415
Functor[3]:
None
Functor[4]:
3.1415

Fixture: Foo(0, 42, 3.1415)
Functor[1]:
a not set.
None
Functor[2]:
a not set.
None
Functor[3]:
a not set.
None
Functor[4]:
a not set.
None

Fixture: Foo(1, 0, 3.1415)
Functor[1]:
b not set.
1
Functor[2]:
b not set.
1
Functor[3]:
b not set.
1
Functor[4]:
b not set.
1

Fixture: Foo(1, 42, 0)
Functor[1]:
c not set.
42
Functor[2]:
c not set.
0
Functor[3]:
c not set.
42
Functor[4]:
c not set.
42
[Finished in 0.0s]

【讨论】:

    【解决方案5】:

    这是我在不删除打印语句的情况下会做的事情

    def function1(self):
        a = self.get_a()
        b = self.get_b()
        c = self.get_c()
        r = None
    
        inputValues = [a, b, c]
        setValues = [i for i in inputValues if i]
        for index, value in inputValues:
            if len(setValues) <= index or setValues[index] != value:
                print(f'{value} is not set.')
            else:
                r = value
        return r
    

    function2 看起来不错。

    【讨论】:

      猜你喜欢
      • 2017-04-06
      • 1970-01-01
      • 2011-02-19
      • 2010-10-20
      • 1970-01-01
      • 2013-02-23
      • 1970-01-01
      • 2013-02-08
      • 1970-01-01
      相关资源
      最近更新 更多