User:Xyy23330121/Python/处理异常
我们之前已经接触到一些报错了。比如在可变序列操作表内,就列出了许多会因为“输入的数据与函数需求不符”导致的报错。
这些报错除了在试运行程序时,可以简洁直白地说明错误位置和错误原因。通过以下的内容,还可以用作代码的一部分。
try
语句会先执行 try
子句,如果没有触发异常,则跳过 except
子句。如果触发异常,则会跳过 try
子句剩下的部分,执行 except
子句。
我们看以下示例:
while True:
try:
x = float(input("请输入一个数字: "))
break
except:
print("请重新输入。")
该示例会要求用户一直输入内容,直到输入有效的数字为止。
或异常类的实例。
我们可以在 except
后面添加异常的类型,来匹配异常。
对于一个 except
语句。当引发的异常不是 except
右侧给出的异常类型(或其子类)的实例时,跳过该 except
语句。
当引发的异常是右侧异常类型的子类,或异常类型元组中某个类型的子类时,会执行该 except
子句,并跳过之后的所有的 except
语句。
我们看以下示例:
while True:
try:
x = float(input("请输入一个数字: "))
break
except ValueError:
#当 try 子句中引发的异常是ValueError或其子类的实例时,执行以下语句。
print("抱歉,输入的并不是有效的数字,请重新输入。")
except (AttributeError, TypeError):
#当 try 子句中引发的异常是AttributeError或TypeError或两者子类的实例时,执行以下语句。
pass
except Exception:
#只要引发可处理的异常,就执行以下语句。
pass
和以下示例:
while True:
try:
x = float(input("请输入一个数字: "))
break
except ValueError:
#当 try 子句中引发的异常是ValueError或其子类的实例时,执行以下语句。
print("抱歉,输入的并不是有效的数字,请重新输入。")
except Exception:
#只要引发异常,执行以下语句。
pass
except (AttributeError, TypeError):
#以下语句不会被执行,因为以上AttributeError和TypeError都是Exception的子类。
#总是会先匹配到 Exception 并跳过对 AttributeError 和 TypeError 的匹配。
pass
以上的 except Exception:
和 except:
是完全等同的。
try:
raise Exception
except Exception as e:
print(type(e)) #输出:<class 'Exception'>
利用 as
关键词,我们可以将所引发的异常赋值给其后的变量名,并在处理异常时访问关于异常的信息。
我们可以用 raise 语句主动引发异常。比如以下示例:
raise Exception
print("由于已经引发异常,本内容不会被输出。")
其输出为:
Traceback (most recent call last):
...
raise Exception
Exception
raise 语句中,raise
后面跟着的是一个异常类型,或异常类型的实例。
对于大多数异常类型,我们可以创建带有注释的实例。比如:
a = Exception('注释')
raise a
这在许多情况下很有用。
特别的,如果在 except
子句中,可以这样使用 raise
语句:
try:
1/0
except:
print('引发了错误')
raise
其输出为:
引发了错误
Traceback (most recent call last):
...
1/0
~^~
ZeroDivisionError: division by zero
此时,raise
语句会原封不动地将正在处理的错误重新引发一遍。
为了表示一个异常是另一个异常的直接后果,可以用这种方法:
raise CausedException from Exception
比如以下示例:
try: 1 / 0
except ZeroDivisionError as e:
raise ValueError("除数不能为零") from e
其输出为:
Traceback (most recent call last):
...
try: 1 / 0
~~^~~
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
...
raise ValueError("除数不能为零") from e
ValueError: 除数不能为零
如果在处理异常时,又引发一个异常,则会将正在处理的异常加入到新异常的注释中,比如:
try: 1 / 0
except ZeroDivisionError:
raise ValueError
其输出为:
Traceback (most recent call last):
...
try: 1 / 0
~~^~~
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
...
raise ValueError
ValueError
作为 raise
语句的特殊用法。如果使用 raise Exception from None
,则会禁用异常链。
try: 1 / 0
except ZeroDivisionError:
raise ValueError from None
其输出为:
Traceback (most recent call last):
...
raise ValueError from None
ValueError
一切引发的异常,都是异常类或异常类的实例。而一切异常类型都是 BaseException
的子类。
由于 BaseException
的一些子类包含“系统退出”等异常——此类异常一旦引起,Python 必须退出,因此不能对该类异常进行处理,也不应在代码中匹配 BaseException
类。
可处理的异常的基类是 Exception
。所有内置的非系统退出类的异常都是该类的子类。所有用户自定义异常也应当是该类的子类。
- args
包含错误信息的元组。对于一般的异常,该元组只包含一个给出错误信息的字符串。对于一些特殊的内置异常,比如OSError
,该元组中的元素可以有特殊的含义。 - with_traceback(traceback)
该方法为异常提供了内置异常中,提示错误位置的部分。 - __traceback__
with_traceback
方法中,用于存储该方法提示的属性。 - add_note(note)
该方法向该异常的注释列表添加注释。该方法一般由 Python 在创建“异常链”时自动调用,读者不必掌握该方法的内容。 - __notes___
异常的注释列表。
我们可以通过e = Exception(*args)
方法创建异常。其中传入args
的内容会原封不动放置到 e.args
里面。
我们一般不手动创建 traceback 对象,而是在引发错误时提取错误的 traceback 对象并进行使用。比如:
import sys
try:
1/0
except:
print('引发了错误')
tb = sys.exception().__traceback__
raise Exception().with_traceback(tb) from None
我们之前已经可以自定义异常的属性,现在我们可以自定义异常的类型。
自定义异常类型的方法和一般的类型是一致的,不过要求它必须继承自Exception
,比如:
class MyException(Exception): pass
class MyException1(ZeroDivisionError): pass
对于大多数情况,引发任何内置异常的或自定义的异常,都足以满足程序逻辑的需求。但随意引发的异常对于修复异常是没有帮助的。
读者在引发异常或自定义异常时,应注意提供详实的异常信息,并使用正确的内置异常、或自定义一个明确的异常名称。
try
语句的结构中,还可以添加 finally
语句和 else
语句。其顺序可以如下表示:
try: pass
except: pass
else: pass
finally: pass
当 try
子句没有引发任何异常时,如果有 else
语句,则会执行其子句。
无论之前异常的处理情况如何,finally
子句都会被执行。具体来讲:
- 如果所有异常都被匹配并处理,则正常执行
finally
子句。 - 如果有异常未被处理,则执行
finally
子句,然后再引发异常。
比如:
try:
1/0
except IndexError:
pass
finally:
print("先执行finally,再引发异常")
先执行finally,再引发异常
Traceback (most recent call last):
...
1/0
~^~
ZeroDivisionError: division by zero
except* 语句用于处理异常组。
异常组是一种特殊的异常。其创建方法为:
ExceptionGroup(msg, excs)
其中,msg
是一个包含错误信息的字符串,而 excs
是包含多个异常的可迭代序列。excs
中的异常只能为 Exception
的子类。
类似 Exception
和 BaseException
,也有 BaseExceptionGroup
。与 ExceptionGroup
不同的是,BaseExceptionGroup
中可以包含 BaseException
的子类。
try:
raise ExceptionGroup("异常组",
[ValueError("错误:ValueError"),
TypeError("错误:TypeError1"),
TypeError("错误:TypeError2")])
except* TypeError as e:
print(f'捕捉到{e.exceptions}')
except* ValueError as e:
print(f'捕捉到{e.exceptions}')
输出为:
捕捉到(TypeError('错误:TypeError1'), TypeError('错误:TypeError2'))
捕捉到(ValueError('错误:ValueError'),)
except* 语句会有选择地匹配异常组中、符合的所有异常,并由下一个 except* 语句来尝试匹配剩下的部分。
对应创建异常组时,使用的参数 msg
。
对应创建异常组时,使用的参数 excs
。它包含参数中所有异常组成的元组。
该属性为只读属性。
返回一个当前异常组中、匹配异常类型 condition
的异常组成的子组。如果没有匹配的异常,则返回 None
。
子组将和原异常组有相同的 exception 信息。
返回一个有两个元素的元组。元组第一个元素即为 self.subgroup(condition)
,而第二个元素为剩余的异常组成的子组。
子组将和原异常组有相同的 exception 信息。
由 subgroup
方法和 split
方法调用的、创建子异常组的方法。
如果要自定义异常组的子类,则应当重写该方法。比如:
class MyGroup(ExceptionGroup):
def derive(self, excs):
return MyGroup(self,message, excs)
要注意,BaseExceptionGroup
定义了 __new__
方法。因此,在创建子类实例时,如果要更改构造子类实例的参数名称或顺序,或给子类添加属性,应当重写 __new__
方法,而非重写 __init__
方法。比如:
class MyGroup(ExceptionGroup):
def __new__(cls, errors, exit_code):
self = super().__new__(Errors, str(exit_code), errors)
self.exit_code = exit_code
return self
def derive(self, excs):
return MyGroup(excs, self.exit_code)