Python进阶小技巧3:跳出多层循环

以前学python的时候一度对如何跳出多层循环比较困惑,总觉得所有方法都不够pythonic。今天突然想起这茬,就觉得写篇文章讨论一下这个问题。

1. 你知道python也有goto吗?

1.1 官方的goto

先来看看python中的goto是怎么写的

from goto import goto, label
for i in range(9):
    for j in range(9):
        for k in range(9):
            print("I am trapped, please rescue!")
            if k == 2:
                goto .breakout # 从多重循环中跳出
label .breakout
print("Freedom!")

看完代码,不要急着复制出来运行,也不要急着pip install goto。首先这段代码在你的环境里肯定是跑不起来的,不管是python2.7还是python3,都不行,因为它属于古老的python2.3,而且即使在古老的2.3中,它也是作为一个愚人节玩笑存在的,并不是真的让你在生产环境里用goto的。

既然代码都贴了,顺便贴一下运行结果吧

I'm trapped, please rescue!
I'm trapped, please rescue!
Freedom!

1.2 非官方的goto

实在非要用goto的话,就看这里https://github.com/snoack/python-goto。这个项目的作者也表示了There is another module that has been released as April Fool's joke in 2004.

goto这个东西吧,就像泡面,偶尔来上一碗,香气扑鼻,但要你天天吃,那就恶心了。很多人都会教你千万别用goto,但依我浅见,这个东西偶尔用一下还是挺香的。

2. 为什么不试试return呢?

上面聊了半天goto,害得我忘记了这篇文章是要讲跳出多层循环的,回到主题。通常情况下,我会选择将多层循环封装成一个函数,然后在需要跳出循环的地方return。照这个道理把上面那段代码重构一下:

def foo():
    def sub_foo():
        for i in range(9):
            for j in range(9):
                for k in range(9):
                    print("I am trapped, please rescue!")
                    if k == 2:
                        return
    sub_foo()
    print("Freedom!")


foo()

虽然这么多缩进看起来有点恶心,但我觉得这是跳出多层循环最pythonic的写法了。

与return较为类似的,就是使用try-except,需要跳出的地方raise一下。但在我心目中raise是用来抛出异常的,用它来跳出循环的话,那就和科比的投篮选择一样,不合理。但是python官方文档在解释为什么不提供goto的时候,就抬出了使用raise来替代的方法。所以谈不上对错吧,用不用就看个人代码风格了。

3. for循环也能用else

合理使用else,也能顺利的跳出多层循环,但是代码可读性有点闹心,look:

for i in range(9):
    for j in range(9):
        for k in range(9):
            print("I am trapped, please rescue!")
            if k == 2:
                break
        else:
            continue
        break
    else:
        continue
    break
print("Freedom!")

是不是看着有点闹心,这种代码的可读性是比较差的,别说其他人了,你写完隔一个月自己都搞不清这段逻辑。

4. 立flag

答应我,别立flag,好吗。

在flag和goto之间二选一来跳出循环,我会选择goto。

Q:如何维护使用大量flag的代码?
A:加入更多的flag。

不知道是不是深受谭浩强影响的缘故,好多恶臭的代码都会充满一堆不明所以的flag,而且非要把它命名成flag吗?flag1,flag2,到flag正无穷,求求你当个人吧,别这么写了。

我觉得这样的flag尤其恶劣:

if k == 2:
    flag = True

不行给它换个名字呗

if k == 2:
    is_k_eq_2 = True

虽然这样有点长有点搓,但起码一看就知道这个标志用于记录k是否等于2。等等,我怎么开始写代码整洁之道了……