例如,当我们需要跳出嵌套循环时如下:
for a in list_a:
for b in list_b:
if condition(a,b):
break
break
关键字keyword只能帮助我们跳出最内层的循环inner-most loop。我们能直接同时跳出两个嵌套循环two nested loops吗?Python 中是否有一些内置关键字built-in keywords或技巧tricks?
遗憾的是,该操作没有内置支持no built-in support。
俗话说:“比较是快乐的小偷comparison is the thief of joy”。Python 做不到这一点,但其他语言可以,比如 PHP:
foreach ($a_list as $a)
{
foreach ($b_list as $b)
{
if (condition($a, $b))
{
break 2; //break out of 2 loops
}
}
}
在 PHP 中, break
关键字接受一个可选的数字,该数字决定了要跳出多少个嵌套循环nested loops。默认值为 1
,表示跳出最内层的循环inner-most loop。
这是一个非常简洁明了的解决方案。PHP 在这里确实更加优雅。
但这并不意味着我们必须现在就开始学习 PHP。因为 Python 非常灵活,我们有很多其他方法可以在没有语法支持的情况下获得相同的结果。
本文将介绍 Python 中跳出嵌套循环break out of nested loops的 5 种方法。最后,它还会提到如何在可能的情况下避免嵌套循环问题。希望您在阅读后能重拾对 Python 的信心。
这是一个有效的解决方案effective solution。我们定义一个变量variable,并将其作为标志flag。下面我们来看一个简单的例子:
# add a flag variable
break_out_flag = False
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
break_out_flag = True
break
if break_out_flag:
break
如上所示, break_out_flag
变量是一个很好的信使messenger,可以告诉程序何时应该跳出外循环break out of the outer loop。
虽然效果不错,但我们的代码有点不整齐,因为我们添加了一个新变量variable来解决这个简单的问题。这并不是绝对必要的。
让我们来看看其他选择。
如果我们不能按预期使用 break
关键字keyword。为什么不换一种方式来实现操作呢?在 Python 异常处理技术exception handling techniques 的帮助下,我们可以跳出嵌套循环,如下所示:
# raise an exception
try:
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
raise StopIteration
except StopIteration:
pass
正如上述程序所示,我们可以将 “中断break” 视为 “异常exception”,并将其抛出throw嵌套循环nested loops。
由于一个条件condition会导致中断breaking,因此在每个循环中检查相同的条件condition也是一个可行的解决方案feasible solution。比如下面的例子:
# check the same condition again
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
break
if j == 2 and i == 0:
break
上述方法可行,但不是个好主意。至少效率不高。因为多次检查同一件事会浪费很多时间。
Python 有一种特殊的语法special syntax:“for-else”。它并不流行not popular,甚至有人从来不知道它。因为每个人的习惯都是在 "if "后面使用 “else”。
然而,当涉及到跳出嵌套循环时。这种非常规的语法可以提供帮助。
# use the for-else syntax
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
break
else: # only execute when it's no break in the inner loop
continue
break
上面的代码利用了 “for - else” 技术,因为 else
语句下的代码只有在内层inner loop循环完成且没有任何中断any breaking的情况下才会执行。
如果你还不熟悉 “for-else” 语法,请看看下面的代码。这是 “for-else” 示例的 “翻译”,即等效代码。
# the same as for-else syntax
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
break
if not (j == 2 and i == 0):
continue
break
总之,这种方法是可行的,但我们必须熟悉奇怪的 “if-else” 语法syntax。
如果我们将嵌套循环nested loops放入一个函数function中,破解问题就变得简单了。因为我们可以使用 return
关键字keyword,而不是 break
。
# make it as a function
def check_sth():
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
return
check_sth() # Run the function when needed
如上所示,这种解决方案solution看起来更优雅more elegant。没有标志变量no flags variables,没有 “try-except” 或 “for-else” 语法syntax,也没有不必要的条件检查condition checking。
此外,“将谓词循环转化为谓词函数Turn Predicate Loops into Predicate Functions” 是 LLVM 编译器基础架构团队推出的一种良好的编码实践。
函数Functions在 Python 中非常灵活flexible。我们可以很容易地定义嵌套函数nested functions或闭包closures。因此,如果嵌套循环nested loops只在另一个函数function中使用一次,我们只需在外部函数outer function中定义即可:
def out_func():
# do something
def check_sth():
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
return
# do something
check_sth() # Run the function when needed
# do something
然而,仅为两个 for 循环定义嵌套函数似乎也不太优雅。
如果没有优雅的解决方案来跳出嵌套循环nested loops,为什么不避免编写嵌套循环呢?如果可能的话,我们会巧妙地将困难的问题变成更简单的问题。
通过使用一些辅助函数helper functions,我们确实可以避免嵌套循环avoid nested loops:
# Avoid nested loops
import itertools
for i, j in itertools.product(range(5), range(5)):
if j == 2 and i == 0:
break
如上所示,在 itertools.product
函数的帮助下,我们前面的示例可以避免嵌套循环。 product
函数会根据输入的可迭代对象iterables 生成笛卡尔积Cartesian product。
遗憾的是,这种方法无法避免所有嵌套循环nested loops。例如,如果我们需要在循环中处理无限的数据流,这种方法就无能为力。
注意:在 product()
运行之前,它会完全消耗输入的可迭代对象iterables,在内存中保留数值池以生成乘积。因此,它只适用于有限的输入。
但是,避免嵌套循环avoid nested loops和提高程序的可读性improve readability仍然是个好主意。
在 Python 中,我们至少有五种跳出嵌套循环break out of nested loops的可行方法。它们都不如 PHP 的方法优雅,但至少我们可以实现这一操作。幸运的是,如果我们能借助 itertools.product
函数将嵌套循环nested loops转换为更简单的循环simpler loop,我们就不必使用嵌套循环nested loops了。