01. Python异常机制

1.1 异常的概念

1.1.1 语法错误

  • 语法错误又称为解析错误,是学习Python时最常见的错误。
  • 在IDE中,这类错误会被标记出来:

image.png

  • 在程序运行时,Python解释器会复现错误的代码行,并用小箭头^指向行里检测到的第一个错误。
    • 如下图中,在print("Hello World")的p下方出现了小箭头。
    • 说明在p前后出现了语法错误(这里就是while True语句后缺少了冒号:与缩进)。

image.png

1.1.2 异常

  • 即使语句或表达式使用了正确的语法,执行时仍可能触发错误。
  • 执行时检测到的错误称为异常,异常会导致程序运行终止,并且解释器会报出错误信息。 ```python

    除数为0异常

    10 * (1 / 0) Traceback (most recent call last): File ““, line 1, in ZeroDivisionError: division by zero

标识符未定义异常

4 + spam * 3 Traceback (most recent call last): File ““, line 1, in NameError: name ‘spam’ is not defined

类型计算异常

‘2’ + 2 Traceback (most recent call last): File ““, line 1, in TypeError: can only concatenate str (not “int”) to str ```

1.1.3 错误信息解读

  • 对于整个报错信息而言,最有用的实际上是最后一行信息。
  • 错误信息的最后一行说明程序遇到了什么类型的错误。
    • 异常有不同的类型,而类型名称会作为错误信息的一部分中打印出来:ZeroDivisionError、NameError、TypeError、……。
    • 作为异常类型打印的字符串是发生的内置异常的名称。
    • 对于所有内置异常都是如此,但对于用户定义的异常则不一定如此(虽然这种规范很有用)。标准的异常类型是内置的标识符(不是保留关键字)。
  • 此行其余部分根据异常类型,结合出错原因,说明错误细节。

    1.2 Python异常体系与结构层次

  • 大部分可见到的异常都是Exception的子类。

  • 底部的Warning只是一个警告,不会报错,也不会终止程序的运行。

    1. BaseException
    2. +-- SystemExit
    3. +-- KeyboardInterrupt
    4. +-- GeneratorExit
    5. +-- Exception
    6. +-- StopIteration
    7. +-- StopAsyncIteration
    8. +-- ArithmeticError
    9. | +-- FloatingPointError
    10. | +-- OverflowError
    11. | +-- ZeroDivisionError
    12. +-- AssertionError
    13. +-- AttributeError
    14. +-- BufferError
    15. +-- EOFError
    16. +-- ImportError
    17. | +-- ModuleNotFoundError
    18. +-- LookupError
    19. | +-- IndexError
    20. | +-- KeyError
    21. +-- MemoryError
    22. +-- NameError
    23. | +-- UnboundLocalError
    24. +-- OSError
    25. | +-- BlockingIOError
    26. | +-- ChildProcessError
    27. | +-- ConnectionError
    28. | | +-- BrokenPipeError
    29. | | +-- ConnectionAbortedError
    30. | | +-- ConnectionRefusedError
    31. | | +-- ConnectionResetError
    32. | +-- FileExistsError
    33. | +-- FileNotFoundError
    34. | +-- InterruptedError
    35. | +-- IsADirectoryError
    36. | +-- NotADirectoryError
    37. | +-- PermissionError
    38. | +-- ProcessLookupError
    39. | +-- TimeoutError
    40. +-- ReferenceError
    41. +-- RuntimeError
    42. | +-- NotImplementedError
    43. | +-- RecursionError
    44. +-- SyntaxError
    45. | +-- IndentationError
    46. | +-- TabError
    47. +-- SystemError
    48. +-- TypeError
    49. +-- ValueError
    50. | +-- UnicodeError
    51. | +-- UnicodeDecodeError
    52. | +-- UnicodeEncodeError
    53. | +-- UnicodeTranslateError
    54. +-- Warning
    55. +-- DeprecationWarning
    56. +-- PendingDeprecationWarning
    57. +-- RuntimeWarning
    58. +-- SyntaxWarning
    59. +-- UserWarning
    60. +-- FutureWarning
    61. +-- ImportWarning
    62. +-- UnicodeWarning
    63. +-- BytesWarning
    64. +-- ResourceWarning

    02. 异常处理

    2.1 异常处理的基本概念

    2.1.1 Python异常处理介绍

  • 所谓异常处理,不是说把这个异常捕获到了,然后去改正这个错误,而是如果程序运行过程中出现了这个错误,Python程序需要用怎样的预备方案去应对。

  • 比如一家发电厂因为一些原因停止发电了(程序运行出现异常导致程序运行中止),但是为了给负责的区域内供电保证正常的运行,发电厂一般都会有备用发电机,当主要发电机不工作时,可以采用备用发电机正常进行发电工作,这就类似于程序中的异常处理。

    2.1.2 Python异常处理流程

  • Python解释器在正常情况下会逐行解释并执行相应的程序代码。

  • 但是在执行过程中,如果遇到了错误,就会抛出这个错误对应的异常对象。
  • 接着Python解释器会检测该异常是否被特定的结构所捕获。

    • 如果该异常已经被捕获了,会直接跳转到捕获异常的处理位置,并执行相对应的预备措施。
    • 如果该异常没有被捕获,则Python程序的运行就会中止,并且解释器会进行报错提示。

      2.2 异常的捕获

  • Python中捕获异常有三种格式:try-excepttry-except-finallytry-except-else

    2.2.1 try-except捕获异常

  • try-except异常捕获的语法格式:

    1. try:
    2. 可能出现异常的代码
    3. except 异常类型1 as 变量名1:
    4. 预备方案1
    5. except 异常类型2 as 变量名2:
    6. 预备方案2
    7. ……
    8. except 异常类型n as 变量名n:
    9. 预备方案n
  • 示例:有一个长度为4的列表nums = [19, 27, 33, 56],试图打印索引位4的元素会报错。

    1. nums = [19, 27, 33, 56]
    2. value = nums[4]
    3. print(value)
    4. """
    5. 运行报错:
    6. Traceback (most recent call last):
    7. File "D:\Project\Python\BaseProject\Day03\global_variable_test.py", line 9, in <module>
    8. value = nums[4]
    9. IndexError: list index out of range
    10. """
    • 此时,就可以捕获这个异常,并给出一个预备方法,如:若出现了下标越界异常,则打印这个列表最后一个元素。
      1. try:
      2. nums = [19, 27, 33, 56]
      3. value = nums[4]
      4. except IndexError as err:
      5. print(err)
      6. value = nums[-1]
      7. print(value)
      8. """
      9. 运行结果:
      10. list index out of range
      11. 56
      12. """
  • 值得注意的是,写在try中的代码,如果出现异常并被捕获,则异常之后的代码也不会被执行。

    1. try:
    2. nums = [19, 27, 33, 56]
    3. value = nums[4] # 这里会出现异常
    4. ele = nums[0] # 这行代码不会被执行
    5. except IndexError as err:
    6. print(err)
    7. value = nums[-1]
    8. print(value)
    9. print(ele) # 报错:NameError: name 'ele' is not defined

    2.2.2 try-except-finally捕获异常

  • try-except-finally应用场景:

    • try-except-finally结构中的try-except与2.2.1中没有区别,finally一般用来让程序与外面资源切断联系。
    • 比如,对数据库的操作,不管try-except是否捕捉到了异常,与数据库的连接都要关闭。
  • try-except-finally异常捕获的语法格式:

    1. try:
    2. 可能出现异常的代码
    3. except 异常类型 as 变量名:
    4. 预备方案
    5. finally:
    6. 不管是否出现异常都执行的操作
  • 示例:用PyMySQL创建一个数据库连接,然后在这个过程中不管是否出现异常,数据库连接都应该被关闭。 ```python import pymysql

connect = None try:

  1. # 简历数据库连接对象
  2. connect = pymysql.Connect(
  3. host='localhost',
  4. port=3306,
  5. user='root',
  6. password='123456',
  7. database='mysql_learning',
  8. charset='utf8mb4'
  9. )
  10. print(connect)

except Exception as e: print(e) finally:

  1. # 只要数据库连接不为空,那么不管try中的程序是否报错,connect都应该被关闭
  2. if connect is not None:
  3. connect.close()
  1. <a name="NNTwQ"></a>
  2. #### 2.2.3 try-except-else捕获异常
  3. - else的运用位置概述:
  4. - 之前的else都是声明在`if-else`、`if-elif-else`语句中的,当if和elif中所有的条件判断语句都为False时,会执行else中的语句。
  5. - 此外,else还可以是`while-else`和`for-else`,当循环正常退出时(while循环的条件为False、for循环遍历完序列中所有元素时)就会执行else中的语句。
  6. ```python
  7. s = ""
  8. for i in "ABC":
  9. s += i.lower()
  10. else:
  11. print(s)
  12. """
  13. 程序运行结果:abc
  14. """
  15. i = 15
  16. while i >= 10:
  17. i -= 1
  18. if i == 12:
  19. break
  20. else:
  21. print(i)
  22. """
  23. 没有运行结果:程序是被break中止的,并非正常退出的,故else不执行。
  24. """
  • 当else运用到try-except中时,是指try中的所有语句没有出现异常时,执行else中的语句。

    1. try:
    2. num1 = 10
    3. num2 = 20
    4. num3 = num1 + num2
    5. except Exception as e:
    6. print(e)
    7. else:
    8. print(num3)
    9. """
    10. 运行结果:30
    11. try中的语句没有出现任何异常
    12. 故执行else中的print(num3)语句
    13. """
  • 示例:输入一个整数,求它和10的差,只要录入的不是整数,就要一直录入下去,直到是整数为止,然后输出计算结果。

    1. while True:
    2. try:
    3. num = int(input("请输入一个整数:"))
    4. except ValueError as e:
    5. print(e)
    6. else:
    7. result = num - 10
    8. print(f"{result=}")
    9. break

    2.3 finally执行先于return

  • 在函数中使用try-catch-finally结构时,finally的执行优先级会高于return语句。

  • 示例:定义一个div函数,传入两个数,计算这两个数的商并写入到./result.txt文件中。若写入成功,则返回商,否则返回0。 ```python def div(num1, num2) -> int: try:
    1. file = open("./result.txt", "w", encoding="utf-8")
    2. result = num1 / num2 # 当num2为0时,会出现ZeroDivisionError异常。
    3. file.write(f'{result}')
    4. return result
    except ZeroDivisionError as e:
    1. print("分母num2的值不能为0")
    2. file.write(f"{0}")
    3. return 0
    finally:
    1. print("程序执行完成,文件result.txt将被关闭")
    2. file.close()

print(div(10, 2))

  1. - 在第15行调用div函数处打断点,可以查看到整个函数的执行流程。
  2. - 可以发现,当try中的代码运行到第6行的`return result`时,会直接跳到finally中执行finally结构中的语句。
  3. - finally结构中的所有语句都执行完成后,会再跳回到第六行,然后将结果返回。
  4. - 以上说明finally的执行优先级先于return语句。
  5. ![20220905_215741_Trim.gif](https://cdn.nlark.com/yuque/0/2022/gif/2692415/1662386508715-d03fcb3e-bae2-4bfb-8aaf-17955421ec5c.gif#averageHue=%23f2f3fc&clientId=ufae3a633-a831-4&from=drop&height=535&id=u0c685c90&originHeight=688&originWidth=1028&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1699867&status=done&style=none&taskId=u9211a9d2-2d46-465d-bf18-ffb65e0f770&title=&width=800)
  6. <a name="KD51g"></a>
  7. ## 03. 抛出异常
  8. - 抛出异常语法格式:`raise 异常类型(异常信息)`
  9. - 示例:输入一个整型数字,当这个数字大于100时,抛出ValueError异常,异常信息为`num=value过大`
  10. ```python
  11. num = int(input("输入一个数字:"))
  12. if num >= 100:
  13. raise ValueError(f"{num=}过大")