退出for循环

8

我对 Python 还比较陌生,不知道这个代码是否正确:

def func(self, foo):
    for foo in self.list:
        if foo.boolfunc(): return True
    return False

良好的实践。

我能否像上面那样从循环中返回,还是应该像下面这样使用while循环?

def func(self, foo):  
    found = false
    while(not found & i < len(self.list)):
        found = foo.boolfunc()
        ++i
    return found

我的Java教授警告我们不要在循环中使用break语句,但是这其实不是一个break语句,而且更加简洁,所以......嗯

谢谢


1
你的另一种版本不是很好,Python。使用and来表示逻辑与。除非你在进行位运算,否则避免使用& - S.Lott
12
你的Java教授是错误的。 - SLaks
1
你的Java教授让你思考这个问题,是在帮助你。 - S.Lott
foo是循环变量,为什么要将foo传递给函数? - codeape
@codeape,我认为foo只是这个例子中的占位符。 - John La Rooy
6个回答

14

你的示例没有问题,但最好写成

def func(self, foo):
    return any(foo.boolfunc() for foo in self.list)

谢谢,我还不熟悉Python的细节,但是这样的东西让我想学更多。 - thepandaatemyface
1
any()的文档:http://docs.python.org/library/functions.html#any。生成器表达式的文档:http://docs.python.org/tutorial/classes.html#generator-expressions。 - codeape

9

需要提到的是,在Python中,for循环可以有一个else子句。只有当循环通过列表的耗尽而终止时才执行else子句。

因此,您可以编写以下代码:

def func(self):
    for foo in self.list:
        if foo.boolfunc():
            return True
    else:
        return False

6
您的教授正在倡导一种被称为“单一返回点”的实践方法,基本上是指任何代码块只应该有一个退出点。循环中的break语句与此有些不同,但通常被视为相同。这是一个颇具争议的观点,肯定没有像“永远不要使用goto”那样严格的规则。也许按照他的方式做是值得的——您可以取悦他,同时也能看到两种方法的好处,但如果违反它会使代码更易读,则大多数人可能不会过于严格遵守单一返回点原则。

5
“提前退出”是一种完全符合Python风格的方式(当有必要时:gnibbler的回答正确地指出,对于您特定的用例,Python 2.5及更高版本中有一个内置的方法是更可取的),并且通常有助于实现Pythonic的目标:“扁平比嵌套更好”。在应用级别上,for通常是执行循环的正确方法:如果有任何复杂的循环逻辑(如可能需要while),通常更好的做法是将其推到辅助生成器中。
有时候会声称选择“单一退出点”的优点包括存在单个“退出瓶颈”,可以在其中执行清理操作。但由于异常总是可能发生的,即使你有一个单独的return作为唯一的退出瓶颈,你也不知道它是否是唯一的:确保良好清理的正确方法是使用with语句(在2.6中或带有“import from the future”的2.5;对于旧版Python,仍然有用但笨重的try/finally)。

Knuth的文章《带有Goto语句的结构化编程》(pdf)是一篇令人难以置信的、具有远见卓识的文章,由许多人认为是计算机科学中理论和实践的传奇人物所写,值得一读。任何怀疑该文章适用于Python的人都应该考虑以下摘自文章的简短引用:

像缩进这样的设备,而不是 分隔符,可能成为表达局部结构的 可行方式。

在Python 1.0发布之前二十年,Knuth就已经预见到了成为Python(以及独立于Python发布比Python早一点的Haskell)标志性语法方面的关键要素(通过与Knuth、Peyton Jones和van Rossum的个人讨论,我可以证明他们三个“用于分组的缩进”的发明完全独立于彼此,只是“伟大的思想相似”——或者用Charles Fort的话来说,“这只是蒸汽机时代”;-)。

正式证明带有早期退出的代码并不比只有单一退出点的等效代码更难,这是因为Corrado Böhm和Giuseppe Jacopini的著名证明展示了如何从任何流程图进行机械转换,使其仅包含序列、选择和重复(但是当然,转换后的程序——就像任何试图避免早期退出风格的其他程序一样——容易出现比必要的嵌套更多的问题,并且需要额外的布尔“状态”变量,这会影响可读性、直接性和效率——在支持良好的语言中,早期退出要好得多)。

@Konrad,很高兴你喜欢它!-) - Alex Martelli

2

这是一个好的实践。

我的Java教授警告我们永远不要在循环中使用break。

为什么呢?“永远不要”是计算机科学中的强词,不应该(…)被使用。1)


1)除了“goto”之外...我们都有自己的偏爱。;-)


他说这是一个坏习惯,几乎总是可以用更好的东西来替代。 - thepandaatemyface
几乎总是可以替换掉一个break语句,但是否更好则取决于个人观点。 - jk.
@jk:总是可以用退出条件替换break。实际上,这有一个证明,它就是“结构化编程的证明理论中的控制流”。 - S.Lott
@Konrad Rudolph:对于学习编程的学生来说,“从不”永远不够强烈。对于学生们,你总是必须使用绝对语言,否则他们永远无法理解边缘条件真正意义上的边缘条件。 - S.Lott
1
@S.Lott:没错,我也使用它,即使在一些有争议的话题上,我必须承认。但是这里教授是错误的(即不同意我的观点)。;-) - Konrad Rudolph
显示剩余2条评论

-2
“跳出循环”很容易变成不良实践,因为它掩盖了循环的终止条件。
如果您的if语句比较复杂,那么循环所建立的后置条件可能会变得不清晰。
如果您的退出条件是明显的,则早期退出是一种常见的语法优化。
如果您的退出条件不明显,则一个复杂、嵌套、难以跟踪的if语句集合会给您带来更多的伤害而不是好处。
事实上,this SO question表明,简单的try块的存在可能导致条件如此令人困惑,以至于产生了明显错误的代码,并且很难进行调试。
通常,所有“早期退出”的事情(特别是break语句)都会导致创建正式证明时出现问题。(else语句也有类似的问题。)

如果你通过推理出创建必要后置条件所需的语句来开发程序,你将永远不需要使用break或早期return,因为你无法轻松地使用形式化方法创建这些语句。

此外,请注意,任何建议避免使用breakelse都会引起憎恨邮件和负评。由于无法解释的原因,仔细观察某些编程结构是一件坏事。

"在Python中,是否应该避免使用else语句?

如果带有break(或早期return)的循环的退出条件不是明显的,那么你需要重新调整事物使其变得明显

在这个例子中,它们是明显的,所以特定示例没有问题。


@S.Lott:您认为形式证明的相关性有多大?根据我的经验,根本没有,因为即使是简单的算法也太复杂了,而且无论您是否使用早期退出,这种复杂性都不会改变。实际上,循环不变量也不会改变。(顺便说一句,我没有给您投反对票,您的观点仍然很好。) - Konrad Rudolph
@Konrad Rudolph:它们是必不可少的。在一个相当可用的语言中,“原语”——语句、内置数据类型等——将有证明这些东西实际上按照广告所述工作的证明。有些证明非常正式,而其他证明则比较正式。但是如果没有一定程度的信任,那么人们就会逐渐失去对该语言的信心。 - S.Lott
2
这样的正式方法与编写大量代码的实际情况直接相悖,这可能就是为什么这样的建议会被踩的原因。任何试图跟踪使用10个标志而不是10个返回的复杂例程的人都会立即反对这样的建议。 - Roman Starkov
1
说实话,我从未听说过这个,而且我相当怀疑。当然,数学库会使用正式证明和其他一些东西。但除此之外...没有太大的存在感。事实上,如果以《工作中的编码人员》为例,非常少有优秀的程序员使用正式证明。 - Konrad Rudolph
@romkyns:“实用性”不是问题。问题显然是正确性。如果你的代码很好,你不需要每行都有正式证明。如果你的代码很糟糕,正式证明的思维纪律可以帮助减少糟糕程度。 - S.Lott
@Konrad Rudolph。大多数(98%)的程序员不使用形式化方法。但是,当您阅读操作系统库、数据结构、解释器等的代码时,您会在注释中看到形式化方法的痕迹。许多许多代码片段已经通过形式化方法进行了审查,以确保它们按照广告所述正常工作。证明循环终止或TreeMap中的红黑着色算法实际上满足所有要求并不需要太多的努力。这比编写无尽的单元测试更容易。 - S.Lott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接