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

    技术2022-07-11  145

    目录

    关于这个系列

    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!

     

    Processed: 0.018, SQL: 9