如何在Python中打破多个循环?

How to break out of multiple loops in Python?

给定以下代码(不起作用):

1
2
3
4
5
6
7
while True:
    #snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok.lower() =="y": break 2 #this doesn't work :(
        if ok.lower() =="n": break
    #do more processing with menus and stuff

有什么方法可以让这个工作吗?或者,如果用户满意,我是否需要执行一次检查以断开输入循环,然后执行另一个更有限的检查以断开外部循环?


我的第一直觉是将嵌套循环重构为一个函数,并使用return来中断。


这是另一个简短的方法。缺点是你只能打破外环,但有时这正是你想要的。

1
2
3
4
5
6
7
8
9
10
for a in xrange(10):
    for b in xrange(20):
        if something(a, b):
            # Break the inner loop...
            break
    else:
        # Continue if the inner loop wasn't broken.
        continue
    # Inner loop was broken, break the outer.
    break

这将使用for/else构造,解释于:为什么在for和while循环之后使用"else"?

关键洞察:似乎只有外环总是断裂。但如果内环不断裂,外环也不会断裂。

江户十一〔一〕的说法是这里的魔法。它在for else条款中。根据定义,如果没有内部破裂,就会发生这种情况。在这种情况下,continue巧妙地避开了外部断裂。


PEP 3136建议贴上"中断/继续"标签。guido拒绝了它,因为"如此复杂的代码需要这个特性是非常罕见的"。不过,PEP确实提到了一些解决方法(例如异常技术),而Guido认为,在大多数情况下,使用返回进行重构会更简单。


首先,普通逻辑是有用的。

如果由于某种原因,终止条件不能得到解决,例外情况就是一个倒退计划。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class GetOutOfLoop( Exception ):
    pass

try:
    done= False
    while not done:
        isok= False
        while not (done or isok):
            ok = get_input("Is this ok? (y/n)")
            if ok in ("y","Y") or ok in ("n","N") :
                done= True # probably better
                raise GetOutOfLoop
        # other stuff
except GetOutOfLoop:
    pass

对于这个特定的示例,可能不需要例外。

另一方面,在字符模式应用程序中,我们通常有"y"、"n"和"q"选项。对于"Q"选项,我们希望立即退出。这更为特殊。


对于这种情况,我倾向于认为重构为一个函数通常是最好的方法,但是对于真正需要突破嵌套循环的情况,这里有一个有趣的变体,即@s.lott所描述的异常引发方法。它使用python的with语句使异常引发看起来更好一些。定义一个新的上下文管理器(您只需执行一次)使用:

1
2
3
4
5
6
7
8
9
from contextlib import contextmanager
@contextmanager
def nested_break():
    class NestedBreakException(Exception):
        pass
    try:
        yield NestedBreakException
    except NestedBreakException:
        pass

现在,您可以按如下方式使用此上下文管理器:

1
2
3
4
5
6
7
8
with nested_break() as mylabel:
    while True:
        print"current state"
        while True:
            ok = raw_input("Is this ok? (y/n)")
            if ok =="y" or ok =="Y": raise mylabel
            if ok =="n" or ok =="N": break
        print"more processing"

优点:(1)比较干净(除了块外没有明确的尝试),(2)每次使用nested_break都会有一个自定义的Exception子类;不需要每次都声明自己的Exception子类。


首先,您还可以考虑将获取和验证输入的过程设为一个函数;在该函数中,如果值正确,您只需返回该值,如果不正确,则在while循环中继续旋转。这从本质上消除了您解决的问题,通常可以应用于更一般的情况(突破多个循环)。如果您必须在代码中保留此结构,并且确实不想处理簿记布尔值…

您也可以以下方式使用goto(使用这里的april-fouls模块):

1
2
3
4
5
6
7
8
9
10
11
#import the stuff
from goto import goto, label

while True:
    #snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok =="y" or ok =="Y": goto .breakall
        if ok =="n" or ok =="N": break
    #do more processing with menus and stuff
label .breakall

我知道,我知道,"你不应该使用goto"和所有这些,但它在这种奇怪的情况下工作得很好。


引入一个新变量,用作"循环断路器"。首先给它赋值(false、0等),然后在外部循环中,在中断之前,将值更改为其他值(true、1等)。一旦循环退出,就对该值进行"父"循环检查。让我演示一下:

1
2
3
4
5
6
7
8
9
breaker = False #our mighty loop exiter!
while True:
    while True:
        if conditionMet:
            #insert code here...
            breaker = True
            break
    if breaker: # the interesting part!
        break   # <--- !

如果有一个无限循环,这是唯一的出路;因为其他循环的执行速度确实要快得多。如果有许多嵌套循环,这也可以工作。你可以全部退出,或者只退出几个。无限可能!希望这有帮助!


1
2
3
4
5
6
keeplooping=True
while keeplooping:
    #Do Stuff
    while keeplooping:
          #do some other stuff
          if finisheddoingstuff(): keeplooping=False

或者类似的。您可以在内部循环中设置一个变量,并在内部循环退出后立即在外部循环中对其进行检查,如果合适,可以中断。我有点喜欢goto方法,只要你不介意使用愚人节的笑话模块——它不是Python式的,但它确实有意义。


要在不重构为函数的情况下突破多个嵌套循环,请使用带有内置stopIteration异常的"模拟goto语句":

1
2
3
4
5
6
7
try:
    for outer in range(100):
        for inner in range(100):
            if break_early():
                raise StopIteration

except StopIteration: pass

请参阅关于使用goto语句分解嵌套循环的讨论。


这不是最漂亮的方法,但在我看来,这是最好的方法。

1
2
3
4
5
6
7
8
def loop():
    while True:
    #snip: print out current state
        while True:
            ok = get_input("Is this ok? (y/n)")
            if ok =="y" or ok =="Y": return
            if ok =="n" or ok =="N": break
        #do more processing with menus and stuff

我很确定你也可以用递归来解决问题,但我不知道这是否是你的一个好选择。


如果两个条件都成立,为什么不继续循环呢?我认为这是一种更像Python的方式:

1
2
3
4
5
6
7
8
dejaVu = True

while dejaVu:
    while True:
        ok = raw_input("Is this ok? (y/n)")
        if ok =="y" or ok =="Y" or ok =="n" or ok =="N":
            dejaVu = False
            break

不是吗?

祝你一切顺利。


将循环逻辑分解为一个迭代器,该迭代器生成循环变量并在完成时返回——这里是一个简单的循环逻辑,它以行/列的形式排列图像,直到图像用完或放不下为止:

1
2
3
4
5
6
7
8
9
10
11
def it(rows, cols, images):
    i = 0
    for r in xrange(rows):
        for c in xrange(cols):
            if i >= len(images):
                return
            yield r, c, images[i]
            i += 1

for r, c, image in it(rows=4, cols=4, images=['a.jpg', 'b.jpg', 'c.jpg']):
    ... do something with r, c, image ...

它的优点是将复杂的循环逻辑和处理过程分开。


python while ... else结构中有一个隐藏的技巧,可以用来模拟双中断,而不需要进行太多的代码更改/添加。本质上,如果while条件为假,则会触发else块。无论是例外情况,continuebreak都不会触发else块。有关更多信息,请参阅"python while语句上的else子句"或python doc on while(v2.7)的答案。

1
2
3
4
5
6
7
8
9
10
11
12
while True:
    #snip: print out current state
    ok =""
    while ok !="y" and ok !="n":
        ok = get_input("Is this ok? (y/n)")
        if ok =="n" or ok =="N":
            break    # Breaks out of inner loop, skipping else

    else:
        break        # Breaks out of outer loop

    #do more processing with menus and stuff

唯一的缺点是需要将双中断条件移动到while条件(或添加标志变量)。这种变化也存在于for循环中,其中else块在循环完成后触发。


在这种情况下,正如其他人所指出的,功能分解是一种可行的方法。python 3中的代码:

1
2
3
4
5
6
7
8
9
10
11
def user_confirms():
    while True:
        answer = input("Is this OK? (y/n)").strip().lower()
        if answer in"yn":
            return answer =="y"

def main():
    while True:
        # do stuff
        if user_confirms():
            break


我来这里的原因是我有一个外环和一个内环,就像这样:

1
2
3
4
5
6
7
for x in array:
  for y in dont_use_these_values:
    if x.value==y:
      array.remove(x)  # fixed, was array.pop(x) in my original answer
      continue

  do some other stuff with x

如你所见,它实际上不会转到下一个x,而是转到下一个y。

我发现解决这个问题的方法只是在数组中运行两次:

1
2
3
4
5
6
7
8
for x in array:
  for y in dont_use_these_values:
    if x.value==y:
      array.remove(x)  # fixed, was array.pop(x) in my original answer
      continue

for x in array:
  do some other stuff with x

我知道这是OP问题的一个具体案例,但我张贴它的目的是希望它能帮助人们在保持简单的同时,以不同的方式思考他们的问题。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
break_levels = 0
while True:
    # snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok =="y" or ok =="Y":
            break_levels = 1        # how far nested, excluding this break
            break
        if ok =="n" or ok =="N":
            break                   # normal break
    if break_levels:
        break_levels -= 1
        break                       # pop another level
if break_levels:
    break_levels -= 1
    break

# ...and so on


通过使用函数:

1
2
3
4
5
6
7
8
9
10
11
def myloop():
    for i in range(1,6,1):  # 1st loop
        print('i:',i)
        for j in range(1,11,2):  # 2nd loop
            print('   i, j:' ,i, j)
            for k in range(1,21,4):  # 3rd loop
                print('      i,j,k:', i,j,k)
                if i%3==0 and j%3==0 and k%3==0:
                    return  # getting out of all loops

myloop()

试着通过注释return来运行上述代码。

不使用任何功能:

1
2
3
4
5
6
7
8
9
10
11
12
done = False
for i in range(1,6,1):  # 1st loop
    print('i:', i)
    for j in range(1,11,2):  # 2nd loop
        print('   i, j:' ,i, j)
        for k in range(1,21,4):  # 3rd loop
            print('      i,j,k:', i,j,k)
            if i%3==0 and j%3==0 and k%3==0:
                done = True
                break  # breaking from 3rd loop
        if done: break # breaking from 2nd loop
    if done: break     # breaking from 1st loop

现在,首先按原样运行上述代码,然后尝试运行,从底部逐个注释包含break的每一行。


另一种将迭代减少到单级循环的方法是使用在python参考中指定的生成器。

1
2
3
4
for i, j in ((i, j) for i in A for j in B):
    print(i , j)
    if (some_condition):
        break

您可以将其扩展到循环的任意级别。

缺点是你不能再只打破一个水平。要么全部要么什么都没有。

另一个缺点是它不适用于while循环。我本来想在python上发布这个答案——打破所有的循环,但不幸的是,它是作为这个循环的副本关闭的。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
break_label = None
while True:
    # snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok =="y" or ok =="Y":
            break_label ="outer"   # specify label to break to
            break
        if ok =="n" or ok =="N":
            break
    if break_label:
        if break_label !="inner":
            break                   # propagate up
        break_label = None          # we have arrived!
if break_label:
    if break_label !="outer":
        break                       # propagate up
    break_label = None              # we have arrived!

#do more processing with menus and stuff


如果不喜欢重构成函数,下面这样的小技巧可能就不会有了。

添加了1个break_级别变量来控制while循环条件

1
2
3
4
5
6
7
8
break_level = 0
# while break_level < 3: # if we have another level of nested loop here
while break_level < 2:
    #snip: print out current state
    while break_level < 1:
        ok = get_input("Is this ok? (y/n)")
        if ok =="y" or ok =="Y": break_level = 2 # break 2 level
        if ok =="n" or ok =="N": break_level = 1 # break 1 level

您可以定义一个变量(例如break_语句),然后在出现两个break条件时将其更改为不同的值,并在if语句中使用它来从第二个循环中断。

1
2
3
4
5
6
7
8
9
10
11
while True:
    break_statement=0
    while True:
        ok = raw_input("Is this ok? (y/n)")
        if ok =="n" or ok =="N":
            break
        if ok =="y" or ok =="Y":
            break_statement=1
            break
    if break_statement==1:
        break


将多个循环转换为单个可断开循环的一种简单方法是使用numpy.ndindex

1
2
3
4
5
6
7
8
for i in range(n):
  for j in range(n):
    val = x[i, j]
    break # still inside the outer loop!

for i, j in np.ndindex(n, n):
  val = x[i, j]
  break # you left the only loop there was!

您必须索引到对象中,而不是能够显式地迭代这些值,但至少在简单的情况下,它似乎比建议的大多数答案简单2-20倍。


尝试使用无限生成器。

1
2
3
4
5
6
7
8
9
from itertools import repeat
inputs = (get_input("Is this ok? (y/n)") for _ in repeat(None))
response = (i.lower()=="y" for i in inputs if i.lower() in ("y","n"))

while True:
    #snip: print out current state
    if next(response):
        break
    #do more processing with menus and stuff

解决方案有两种方式

举个例子:这两个矩阵相等吗?< BR>Matrix1和Matrix2大小相同,n,2维矩阵。

第一个解决方案,不带函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
same_matrices = True
inner_loop_broken_once = False
n = len(matrix1)

for i in range(n):
    for j in range(n):

        if matrix1[i][j] != matrix2[i][j]:
            same_matrices = False
            inner_loop_broken_once = True
            break

    if inner_loop_broken_once:
        break

第二个解,带函数
这是我的案例的最终解决方案

1
2
3
4
5
6
7
def are_two_matrices_the_same (matrix1, matrix2):
    n = len(matrix1)
    for i in range(n):
        for j in range(n):
            if matrix1[i][j] != matrix2[i][j]:
                return False
    return True

祝你今天愉快!


我想提醒您,python中的函数可以在代码的正中间创建,并且可以透明地访问周围的变量进行读取,并使用nonlocalglobal声明进行写入。

因此,可以将函数用作"可断开的控制结构",定义要返回的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def is_prime(number):

    foo = bar = number

    def return_here():
        nonlocal foo, bar
        init_bar = bar
        while foo > 0:
            bar = init_bar
            while bar >= foo:
                if foo*bar == number:
                    return
                bar -= 1
            foo -= 1

    return_here()

    if foo == 1:
        print(number, 'is prime')
    else:
        print(number, '=', bar, '*', foo)
1
2
3
4
5
6
>>> is_prime(67)
67 is prime
>>> is_prime(117)
117 = 13 * 9
>>> is_prime(16)
16 = 4 * 4

希望这有助于:

1
2
3
4
5
6
7
8
9
x = True
y = True
while x == True:
    while y == True:
         ok = get_input("Is this ok? (y/n)")
         if ok =="y" or ok =="Y":
             x,y = False,False #breaks from both loops
         if ok =="n" or ok =="N":
             break #breaks from just one

和以前一样,但更紧凑。(布尔值就是数字)

1
2
3
4
5
6
7
8
9
breaker = False #our mighty loop exiter!
while True:
    while True:
        ok = get_input("Is this ok? (y/n)")
        breaker+= (ok.lower() =="y")
        break

    if breaker: # the interesting part!
        break   # <--- !


由于这个问题已经成为进入特定循环的标准问题,我想用Exception给出我的答案。

虽然在多环结构中不存在名为breaking of loop的标签,但是我们可以使用用户定义的异常来中断我们选择的特定循环。请考虑以下示例,其中让我们在Base-6编号系统中打印最多4个数字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class BreakLoop(Exception):
    def __init__(self, counter):
        Exception.__init__(self, 'Exception 1')
        self.counter = counter

for counter1 in range(6):   # Make it 1000
    try:
        thousand = counter1 * 1000
        for counter2 in range(6):  # Make it 100
            try:
                hundred = counter2 * 100
                for counter3 in range(6): # Make it 10
                    try:
                        ten = counter3 * 10
                        for counter4 in range(6):
                            try:
                                unit = counter4
                                value = thousand + hundred + ten + unit
                                if unit == 4 :
                                    raise BreakLoop(4) # Don't break from loop
                                if ten == 30:
                                    raise BreakLoop(3) # Break into loop 3
                                if hundred == 500:
                                    raise BreakLoop(2) # Break into loop 2
                                if thousand == 2000:
                                    raise BreakLoop(1) # Break into loop 1

                                print('{:04d}'.format(value))
                            except BreakLoop as bl:
                                if bl.counter != 4:
                                    raise bl
                    except BreakLoop as bl:
                        if bl.counter != 3:
                            raise bl
            except BreakLoop as bl:
                if bl.counter != 2:
                    raise bl
    except BreakLoop as bl:
        pass

当我们打印输出时,我们将永远不会得到单位位置为4的任何值。在这种情况下,我们不会从任何循环中断,因为BreakLoop(4)被提升并捕获在同一个循环中。同样地,当10个位置有3个时,我们使用BreakLoop(3)进入第三个循环。当百位有5位时,我们用BreakLoop(2)进入第二个循环,当千位有2位时,我们用BreakLoop(1)进入第一个循环。

简而言之,在内部循环中引发异常(内置的或用户定义的),并在循环中捕获它,从中恢复控件。如果要从所有循环中中断,请在所有循环之外捕获异常。(我没有在示例中显示这个案例)。


我解决这个问题的方法是定义一个变量,该变量被引用来确定是否要进入下一个级别。在本例中,此变量称为"shouldbreak"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Variable_That_Counts_To_Three=1
while 1==1:
    shouldbreak='no'
    Variable_That_Counts_To_Five=0
    while 2==2:
        Variable_That_Counts_To_Five+=1
        print(Variable_That_Counts_To_Five)
        if Variable_That_Counts_To_Five == 5:
            if Variable_That_Counts_To_Three == 3:
                shouldbreak='yes'
            break
    print('Three Counter = ' + str(Variable_That_Counts_To_Three))
    Variable_That_Counts_To_Three+=1
    if shouldbreak == 'yes':
        break

print('''
This breaks out of two loops!'''
)

这可以很好地控制程序的中断方式,允许您选择何时中断以及要降低多少级别。