在Python中错误一般分为两种:编码阶段产生的语法错误 和 运行期间由于数据不满足逻辑产生的错误。
一、语法错误
语法错误又称解析错误,是在编码阶段产生的错误,在初期学习Python时最常见的错误:
关键字错误示例:
whileTrue: prin("Hello, World!") # 正确的应该为 print
运行python文件时,解释器会告诉我们错误发生的函数和错误位置,如果是关键字类错误,解释器也会给出可能的建议。
Traceback (most recent call last): File "/home/probie/workspace/python/base-python/test.py", line 2, in <module> prin("Hello, World!") # ^^^^NameError: name 'prin'isnot defined. Did you mean: 'print'?
关键字错误会报 NameError,并给出可能的建议
语法错误示例:
whileTrue: print("hlsshs"# 缺少右边的括号
File "/home/probie/workspace/python/base-python/test.py", line 2 print("hlsshs" ^SyntaxError: '(' was never closed
语法错误会报 SyntaxError,并会告知错误的行数和位置
针对语法错误,我们需要合理借助解释器给出的错误位置,错误函数,并针对性修复,是在编码阶段可发现的错误
二、异常
整个程序不存在语法错误时,就可以进入运行阶段,在运行阶段由于数据不符合程序预期(有些数据异常可以增加防御性拦截减少)发生的错误,或者程序需要访问网络和磁盘时,可能会发生的网络问题(例如超时)或者访问磁盘文件时文件不存在的情况,产生的错误称呼为 异常。
示例:
defdivide(a, b):# if b == 0:# raise ValueError("Denominator cannot be zero.")return a / bprint(divide(10, 2)) # Expected output: 5.0print(divide(10, 0)) # This should raise a ValueError
5.0Traceback (most recent call last): File "/home/probie/workspace/python/base-python/test.py", line 7, in <module> print(divide(10, 0)) # This should raise a ValueError ^^^^^^^^^^^^^ File "/home/probie/workspace/python/base-python/test.py", line 4, in dividereturn a / b ~~^~~ZeroDivisionError: division by zero
同样,异常发生的时候,如果程序没有进行逻辑上的捕获处理,解释器会直接中断后续执行,并抛出错误信息,错误信息包含错误发生的位置,异常的原因。
像上述示例中的 ZeroDivisionError 我们就可以通过防御性的错误,先判断除数是否为0,从而避免程序的直接中断。
1、异常的处理
在Python中异常的处理不是必须的,如果代码逻辑上没有主动进行异常的捕获和处理,在发生异常的时候,解释器会中断执行,可能不是我们预期需要的结果。
为什么需要主动对异常进行捕获和处理呢?我们现在想要往文件里边写入数据,这个时候如果文件不存在或者没有写的权限,如果直接失败,用户体验就可能很差;对于文件不存在,业务上是否可以接收当发生文件不存在异常时,程序主动创建文件,继续后续逻辑的执行;对于没有写的权限,可能就需要对异常进行处理,转为用户可读的信息,返回调整后的描述信息。
异常处理语法:
try:# 可能发生异常的代码except 异常类型: # 可以有多个 except# 或 except 异常类型 as 别名 else: # 可选 paas finally: # 一定会执行的逻辑,可选 paas
关键字解释:
except:用于捕获并匹配异常类型,可以有多个except匹配不同的类型,异常类型的匹配应该遵循从小到达的原则(即先具体异常逐步转为适配度更高的异常类型),同一个except下也可以同时匹配多个异常类型,使用元组配置多个异常类型;except的匹配过程按照从上到下的顺序。as:在except中,对于异常类型定义一个别名,在子句中通过别名对类型进行操作else:可选关键字,表示未发生异常的情况下,需要执行的代码逻辑finall:可选关键字,无论是否发生异常都会执行的代码逻辑,通常用于资源的释放,例如:文件和网络IO资源
语句执行顺序:
- 如果没有触发异常,则跳过
except子句,会执行可选的 else 和 finally 子句 - 如果发生异常,会跳过
try子句中异常位置的后续语句的执行,会按照except的顺序进行异常类型匹配。如果异常的类型与 except关键字后指定的异常相匹配,则会执行 except 子句, - 如果发生的异常与 except 子句 中指定的异常不匹配,则它会被传递到外层的
try语句中;如果没有找到处理器,则它是一个 未处理异常 且执行将停止并输出一条错误消息。
image-202601141702296691.1 无异常示例
defdivide(a, b):try: r = a / bexcept ZeroDivisionError: print("哦豁,除以零了!")else: print("除法成功,结果是:", r)finally: print("无论如何,这段代码都会执行。") print("函数执行完毕。")divide(10, 2)
输出:
除法成功,结果是: 5.0无论如何,这段代码都会执行。函数执行完毕。
从执行结果来说:执行完try子句以后,先执行else在执行finally语句。
1.2 抛出异常,匹配 except
defdivide(a, b):try: r = a / b print("正在执行除法操作...")except ZeroDivisionError: print("哦豁,除以零了!")else: print("除法成功,结果是:", r)finally: print("无论如何,这段代码都会执行。") print("函数执行完毕。")divide(10, 0)
输出:
哦豁,除以零了!无论如何,这段代码都会执行。函数执行完毕。
从执行结果来说:执行try子句a/b发生异常以后,后续的print语句不再执行,然后再进行except匹配,当前异常类型与ZeroDivisionError匹配,执行except子句,然后在执行finally语句,其中else未执行。
1.3 抛出异常,无任何except匹配
defdivide(a, b):try: r = a / b print("正在执行除法操作...")except NameError: print("哦豁,除以零了!")else: print("除法成功,结果是:", r)finally: print("无论如何,这段代码都会执行。") print("函数执行完毕。")divide(10, 0)
输出:
无论如何,这段代码都会执行。Traceback (most recent call last): File "/home/probie/workspace/python/base-python/test.py", line 15, in <module> divide(10, 0) File "/home/probie/workspace/python/base-python/test.py", line 3, in divide r = a / b ~~^~~ZeroDivisionError: division by zero
try子句发生异常的时候,如果没有任何except匹配,会先执行finally子句,然后中断程序后续执行。
1.4 抛出异常,except按照从大到小匹配(错误顺序)
defdivide(a, b):try: r = a / b print("正在执行除法操作...")except Exception: print("我是 Exception 捕获的异常!")except ZeroDivisionError: print("我是 ZeroDivisionError 捕获的异常!")else: print("除法成功,结果是:", r)finally: print("无论如何,这段代码都会执行。") print("函数执行完毕。")divide(10, 0)
由于 ZeroDivisionError 是 Exception 的子类,所有except按照从上到下匹配的时候,Exception满足,执行Exception子句,不再进行后续异常类型匹配。
此种顺序会导致抛出的错误信息非预期的精确描述
1.5 同一个 except 匹配多个异常类型
defdivide(a, b):try: r = a / b print("正在执行除法操作...")except (ZeroDivisionError , NameError): print("除法失败,除数不能为零或变量未定义。")else: print("除法成功,结果是:", r)finally: print("无论如何,这段代码都会执行。") print("函数执行完毕。")divide(10, 0)
输出:
除法失败,除数不能为零或变量未定义。无论如何,这段代码都会执行。函数执行完毕。
2、主动触发异常
使用关键字 raise 可以强制触发异常。
何时使用raise主动抛出异常:
- 在捕获到异常以后,对异常添加额外的注释信息或记录相关行为后,抛出转换后的自定义异常--全局异常统一
- 将
raise 作为多层嵌套if的一个替换,在条件不满足的情况下,抛出一个特定的异常,减少嵌套深度
示例:
i = int(input("输入一个整数: "))if i < 0:raise ValueError("负数没有平方根")else: print(i ** 0.5)
输入一个整数: -1Traceback (most recent call last): File "/home/probie/workspace/python/base-python/test.py", line 4, in <module>raise ValueError("负数没有平方根")ValueError: 负数没有平方根
3、使用注释细化异常原因
当一个异常被创建以引发时,它通常被初始化为描述所发生错误的信息。在有些情况下,在异常被捕获后添加信息是很有用的。为了这个目的,异常有一个 add_note(note) 方法接受一个字符串,并将其添加到异常的注释列表。在异常堆栈信息之后按照它们被添加的顺序输出所有的注释。
如果对异常需要增加额外的注释信息时,需要使用 as 将异常定义一个别名
示例:
defdivide(a, b):try: r = a / bexcept ZeroDivisionError as e: e.add_note("我是 1") e.add_note("我是 2")raisereturn rdivide(1, 0)
输出:
Traceback (most recent call last): File "/home/probie/workspace/python/base-python/test.py", line 10, in <module> divide(1, 0) File "/home/probie/workspace/python/base-python/test.py", line 3, in divide r = a / b ~~^~~ZeroDivisionError: division by zero我是 1我是 2
4、自定义异常
通过创建新的类自定义异常,自定义类需要直接或间接的方式继承 Exception 类。Exception 可以被用作通配符,捕获(几乎)一切。大多数异常命名都以 “Error” 结尾,类似标准异常的命名。
BaseException是所有异常的共同基类。它的一个子类, Exception,是所有非致命异常的基类。不是 Exception的子类的异常通常不被处理,因为它们被用来指示程序应该终止。
4.1 基本自定义异常
语法:
class 名称Error(Exception):# 此处Exception可以变更为其他任何继承 Exception 的类 paas
示例
classBaseError(Exception):"""Base class for all custom exceptions in the module."""passclassSubErrorA(BaseError):"""Exception raised for specific error A."""passi = int(input("请输入一个整数:"))if i < 0:raise SubErrorA("输入的整数不能为负数。")elif i == 0:raise BaseError("输入的整数不能为零。")else: print(f"输入的整数是:{i}")
匹配 SubErrorA:
请输入一个整数:-1Traceback (most recent call last): File "/home/probie/workspace/python/base-python/test.py", line 12, in <module>raise SubErrorA("输入的整数不能为负数。")SubErrorA: 输入的整数不能为负数。
匹配BaseError:
请输入一个整数:0Traceback (most recent call last): File "/home/probie/workspace/python/base-python/test.py", line 14, in <module>raise BaseError("输入的整数不能为零。")BaseError: 输入的整数不能为零。
正常输出:
请输入一个整数:2输入的整数是:2
4.2 增加自定义操作异常定义
自定义异常描述格式
需要重写 __str__方法:
class 名称Error(Exception):def__str__(self):# 自定义逻辑return xx
示例:
classCommonError(Exception):def__init__(self, code): self._code = code super().__init__(code)def__str__(self):if self._code == "ERR101":return"输入的整数不能为负数"elif self._code == "ERR102":return"输入的值不是整数"else:return"未知错误"i = int(input("请输入一个整数: "))if i < 0:raise CommonError("ERR101")else: print("输入的整数是:", i)
输出:
请输入一个整数: -2Traceback (most recent call last): File "/home/probie/workspace/python/base-python/test.py", line 16, in <module>raise CommonError("ERR101")CommonError: 输入的整数不能为负数