《最值得收藏的python3语法汇总》之错误和异常

目录

关于这个系列

1、定义

2、处理异常

3、清理行为

4、自定义异常


关于这个系列

《最值得收藏的python3语法汇总》,是我为了准备公众号“跟哥一起学python”上面视频教程而写的课件。整个课件将近200页,10w字,几乎囊括了python3所有的语法知识点。

你可以关注这个公众号“跟哥一起学python”,获取对应的视频和实例源码。

这是我和几位老程序员一起维护的个人公众号,全是原创性的干货编程类技术文章,欢迎关注。


 

1、定义

大家需要知道一个事实,那就是,在一个正式的软件项目代码里面,会有较大比例的代码是在处理程序的错误和异常场景。如果你想让你的程序能足够稳定的运行,那么你在写代码时就必须要考虑到可能出现的所有异常,并且处理它们。

我们看看下面这个例子:

#  author: Tiger,   关注公众号“跟哥一起学python”,ID:tiger-python

# file: ./13/13_1.py

# 错误和异常
def my_divide(a, b):
    x = a / b
    return x

print(my_divide(100, 10))

这个程序运行完全正常,看起来没有任何问题。

但是,如果我们修改一下传入的实参,就会报异常:

print(my_divide(100, 0))

我们将第二个实参,也就是被除数,改为0。以我们的数学常识我们知道,除以0,是不被允许的。程序会报异常并且直接结束:

ZeroDivisionError: division by zero

所以,这个简单的程序就是典型的,只实现了主要功能逻辑,而不考虑异常分支的案例。作为一个软件项目来说,稳定性可靠性的重要程度不亚于功能性。

在Python中存在两种错误:语法错误和异常。

语法错误又称为解析错误,它是python解释器在解析代码的时候报的一种错误。比如:

# 语法错误

def func_xxx()
    pass

很显然,我们在定义函数的时候少了一个冒号。解释器在编译时会给我们明确的错误提示:

File "D:/跟我一起学python/练习/13/13_1.py", line 12

def func_xxx()

                 ^

SyntaxError: invalid syntax

它会列出出错的模块路径,以及出错的代码行行号。同时它会用一个箭头符号^,指向出错的具体地方,并且抛出语法错误的提示invalid syntax。

异常,则是在程序运行过程中产生的错误。比如我前面例子中提到的ZeroDivisionError,就是属于异常的范畴。

 

2、处理异常

异常并不都是代码的bug,通常异常只是代码逻辑的一种特殊场景,它需要我们特殊处理。如果不处理,这个异常会被抛给Python解释器,解释器就会将程序强制退出。

Python中通过try语句来处理异常,其语法如下:

try:

    # 可能会抛出异常的代码段
except Exception1:
    #
对异常Exception1的处理

except Exception2 as e2:
    #
对异常Exception2的处理,可以使用e2获取异常信息

except (Exception1, Exception1,...):

    # 对多个异常同时处理

else:

    # 没有任何异常发生时的处理

finally:

    # 不管有没有异常,都会执行的代码

当try里面的代码段运行时抛出了异常,except会捕捉这个异常,如果except没有捕捉,那么这个异常就会抛给解释器,解释器会强制退出程序。

只有这个异常在except语句的异常列表中,才会被捕捉。Except里面的处理语句可以为空,系统也会认为该异常被处理了。

如果我们不清楚代码段到底会抛出多少异常,我们可以使用except来捕捉异常Exception。因为所有的异常都是从Exception直接或间接派生出来的,所以它可以捕捉所有的异常。但是只捕捉Exception这种做法不被提倡,最好还是去捕捉具体的异常并做相应的处理。

Else里面的代码,在没有任何异常的时候会被执行,这通常可以用来判断try里面的代码段是否执行成功。

Finally里面的代码,在任何情况下都一定会被执行。它通常用来做一些清理工作,比如释放内存、关闭文件等。

我们也可以使用raise关键字抛出异常,下面这个例子中我们抛出python内置的异常,下节我们讲如何自定义异常。

下面我们看一个比较完整的例子:

# 处理异常

def sample_raise(x):
    '''
    不要关注下面抛出异常的具体含义,
    这里仅仅用于演示
    '''
    if x > 100:
        raise MemoryError("x > 100")
    elif 50 > x >= 0:
        raise ValueError("50 > x >= 0")
    elif x < 0:
        raise KeyError("x < 0")
    else:
        return x

try:
    sample_raise(-1)
except MemoryError:
    print('MemoryError is occured!')
except ValueError as e:
    print(f'ValueError, error info is : {e}')
except Exception as e:
    print(f'Exception, error info is : {e}')
else:
    print(f'else branch input')
finally:
    print(f'finally branch input')

输出为:

Exception, error info is : 'x < 0'

finally branch input

当try里面的代码段抛出一个异常后,except是从上往下依次捕捉的,一旦被捕捉到就不会再往下寻找。如果except语句中捕捉了重复的异常,或者我们把父类Exception放在了前面,那很可能会被提前捕捉。

从上面例子我们也可以看到,finally是一定会被执行的,所以它通常用于释放一些资源,完成程序异常时的清理工作。

 

3、清理行为

当程序发生异常时,我们需要做一些清理工作,比如当我们打开了一个文件,如果读取文件时出现异常,那么我们希望在程序退出之前可以把文件关闭。

如果不考虑异常情况,那么我们会这样写这个程序:

#  author: Tiger,   关注公众号“跟哥一起学python”,ID:tiger-python

# file: ./13/13_2.py

# 不考虑异常
fp = open("test.txt")
for line in fp:
    print(line, end="")
fp.close()

这个代码,如果我们不考虑异常,那么在执行的过程中可能会出现UnicodeDecodeError。原因是因为我们的test.txt中存在非gbk编码的字符。程序遇到异常后就直接退出了,fp无法被正常关闭。

所以,我们需要处理异常,并且确保fp一定会被关闭。前面我们学过finally,知道它是肯定会被执行的,所以我们可以把关闭fp的语句写到finally里面。如下:

# 异常情况下finally资源清理

fp = 0
try:
    fp = open("test.txt")
    for line in fp:
        print(line, end="")
except Exception as e:
    print(e)
finally:
    print('finally!')
    if fp != 0:
        fp.close()

而更加pythonic的写法,是使用with语句,它对于预定义了清理行为的对象适用。具体一点,就是能使用的with语句的类对象,一定是定义了__enter__()/__exit__()这两个成员方法的。

object.__enter__(self)

进入与此对象相关的运行时上下文。with语句将将此方法的返回值绑定到语句的AS子句中指定的目标(如果有设置的话)

object.__exit__(self, exc_type, exc_value, traceback)

退出与此对象相关的运行时上下文。参数描述导致上下文退出的异常。如果上下文运行时没有异常发生,那么三个参数都将置为None

如果有异常发生,并且该方法希望抑制异常(即阻止它被传播),则它应该返回True。否则,异常将在退出该方法时正常处理。

 

请注意, __exit__()方法不应该重新抛出传入的异常。

 

File这个对象刚好是定义了这两个成员方法的,所以它可以使用with语句。

# with语句,预清理行为

with open("test.txt") as f:
    for line in f:
        print(line, end="")

with在对象抛出异常时,会自动调用__exit__方法。File对象的__exit__方法里面会关闭文件。

所以,对于这种定义了清理行为的对象,我们采用with会更加方便。

         

4、自定义异常

异常都是直接或者间接继承至Exception类,我们当然可以自己定义一个自己的异常类,只要将其父类指定为Exception类即可。

#  author: Tiger,   关注公众号“跟哥一起学python”,ID:tiger-python

# file: ./13/13_3.py

# 自定义异常
# 以下例子用于计算体重指标BMI,当达到肥胖指标,抛出一个自定义的异常
class WeightError(Exception):
    def __init__(self, bmi, desp_str):
        self.bmi = bmi
        self.desp_str = desp_str

    def __str__(self):
        return f'{self.desp_str}, bmi: {self.bmi}'

def get_bmi(height, weight):
    bmi = round(weight / (height ** 2), 2)
    if 24 <= bmi <= 27.9:
        raise WeightError(bmi, '过重')
    elif bmi > 28.0:
        raise WeightError(bmi, '肥胖')

if __name__ == '__main__':
    try:
        get_bmi(1.65, 70)
    except WeightError as e:
        print(e)
    finally:
        print('finally!')

输出为:

过重, bmi: 25.71

finally!

 

你可能感兴趣的:(《最值得收藏的python3语法汇总》之错误和异常)