< 返回博客

使用for循环来避免Python中try的嵌套


python中,有时候可能会嵌套地调用try:except, 比如这样:

from base64 import b64decode
try:
    base64Decode = b64decode(str)
except:
    try:
        base64Decode = b64decode(str + '=')
    except:
        base64Decode = b64decode(str + '==')

这段代码是我在解析学校锐捷api的时候写的,因为api返回的是没有末尾等号的Base64编码,而pythonbase64库并不支持这样的字符串的直接解析。这样写也可以用,但是不那么pythonic,谷歌了一下,有这种写法:

from base64 import b64decode
for s in (str, str+'=', str+'=='):
    # 避免try的嵌套
    try:
        base64Decode = b64decode(s)
    except:
        continue

这种写法写的很舒服,也很清晰。写代码的时候一定要注意,尽量使代码扁平化,使用尽量少的嵌套,嵌套地过深就成了“嵌套地狱”,写起来不容易,阅读起来不容易,维护时更是令人恼火。异步编程中,回调地狱也是一种嵌套地狱,相比于一般的if的嵌套地狱,回调函数的嵌套更加令人抓狂,而且程序的执行顺序还非常的违反直觉,Promiseasync等等语法形式在某种程度上都是为了解决这个问题。


八月六日整理添加

整理博客的时候看到了这一篇,之前可真的菜的真实。
这篇文章的几个问题:

1. Base64字符串末尾的等号

Base64编码的原理是把三个八位的字节(3x8=24)当做四个六位的部分(4x6=24),给每一部分前面补上两个0,变成了4个八位字节。六位能表示的字符数量比较少,可以全部映射到可打印的字符上,通过Base64提供的一张表来完成这个映射。如果原始的数据的长度不能被3整除,最后会剩下不足三个字节,则应该用0补充,最终的编码输出用等号=
所以,最终的Base64字符串长度一定是可以被4整除的,我们只需要补上相应数量的等号就行了。

padlen = 3 - (len(s) + 3) % 4
s += '=' * padlen
try:
    b64decode(str)
except ...:
    do_something()

这里用到一个技巧。如果按照一般的直觉来写的话,padlen的求法或许是4 - len(s) % 4,但如果s的长度本来就是4的倍数,这样的算法就会得到4,实际上应该是0;所以作为补充,可以改为(4 - len(s) % 4) % 4,再求一次模;但其实3 - (len(s) + 3) % 4这种神奇的求法也可以达到同样的效果,只是不知道如何推导这两者的等价关系。
其实还是推荐用第二种比较好理解的办法。

2. for循环避免try:except嵌套的通用方法

之前的文章中只提到了可以避免文章中所述类型的嵌套,其实我们可以将这种方法进行推广。
比如有如下代码:

try:
    method_a()
except Some:
    try:
        method_b()
    except Some:
        try:
            method_c()
        except Some:
            do_something()

我们可以用下面这种形式替代:

for m in (method_a, method_b, method_c):
    try:
        m()
        break
    except Some:
        do_something()

这样,我们就可以优雅的避免掉任何情况下的嵌套了。
这个推广参考了Python使用for/else...。但是注意一下原文第二种方式中try语句的else,当三个方法中的一个成功后,我们就不需要继续循环了,所以作者加上else。而在我的实现里面,把break放到了try块中m()的后面,也能达到同样的效果,而且看起来更加的清晰,try里面的else还是挺令人疑惑的。

3. 不应该使用str作为变量名

str是python的关键字。虽然在这里不会影响运行,但是不推荐。