User:Xyy23330121/Python/处理异常


我们之前已经接触到一些报错了。比如在可变序列操作表内,就列出了许多会因为“输入的数据与函数需求不符”导致的报错。

这些报错除了在试运行程序时,可以简洁直白地说明错误位置和错误原因。通过以下的内容,还可以用作代码的一部分。

try 语句 编辑

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 语句主动引发异常。比如以下示例:

raise Exception
print("由于已经引发异常,本内容不会被输出。")

其输出为:

Traceback (most recent call last):
  ...
    raise Exception
Exception

raise 语句中,raise 后面跟着的是一个异常类型,或异常类型的实例。

对于大多数异常类型,我们可以创建带有注释的实例。比如:

a = Exception('注释')
raise a

这在许多情况下很有用。

except: raise 编辑

特别的,如果在 except 子句中,可以这样使用 raise 语句:

try:
    1/0
except:
    print('引发了错误')
    raise

其输出为:

引发了错误
Traceback (most recent call last):
  ...
    1/0
    ~^~
ZeroDivisionError: division by zero

此时,raise 语句会原封不动地将正在处理的错误重新引发一遍。

raise from 编辑

为了表示一个异常是另一个异常的直接后果,可以用这种方法:

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 from None 编辑

作为 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 。所有内置的非系统退出类的异常都是该类的子类。所有用户自定义异常也应当是该类的子类。

异常的属性与方法 编辑

  1. args
    包含错误信息的元组。对于一般的异常,该元组只包含一个给出错误信息的字符串。对于一些特殊的内置异常,比如 OSError,该元组中的元素可以有特殊的含义。
  2. with_traceback(traceback)
    该方法为异常提供了内置异常中,提示错误位置的部分。
  3. __traceback__
    with_traceback方法中,用于存储该方法提示的属性。
  4. add_note(note)
    该方法向该异常的注释列表添加注释。该方法一般由 Python 在创建“异常链”时自动调用,读者不必掌握该方法的内容。
  5. __notes___
    异常的注释列表。

创建异常对象 编辑

我们可以通过e = Exception(*args)方法创建异常。其中传入args的内容会原封不动放置到 e.args 里面。

traceback 编辑

我们一般不手动创建 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

异常的提示功能 编辑

对于大多数情况,引发任何内置异常的或自定义的异常,都足以满足程序逻辑的需求。但随意引发的异常对于修复异常是没有帮助的。

读者在引发异常或自定义异常时,应注意提供详实的异常信息,并使用正确的内置异常、或自定义一个明确的异常名称。

finally 与 else 编辑

try 语句的结构中,还可以添加 finally 语句和 else 语句。其顺序可以如下表示:

try: pass
except: pass
else: pass
finally: pass

else 编辑

try 子句没有引发任何异常时,如果有 else 语句,则会执行其子句。

finally 编辑

无论之前异常的处理情况如何,finally 子句都会被执行。具体来讲:

  1. 如果所有异常都被匹配并处理,则正常执行 finally 子句。
  2. 如果有异常未被处理,则执行 finally 子句,然后再引发异常。

比如:

try:
    1/0
except IndexError:
    pass
finally:
    print("先执行finally,再引发异常")
先执行finally再引发异常
Traceback (most recent call last):
  ...
    1/0
    ~^~
ZeroDivisionError: division by zero

异常组与 except* 编辑

except* 语句用于处理异常组。

异常组 编辑

异常组是一种特殊的异常。其创建方法为:

ExceptionGroup(msg, excs)

其中,msg 是一个包含错误信息的字符串,而 excs 是包含多个异常的可迭代序列。excs 中的异常只能为 Exception 的子类。

类似 ExceptionBaseException,也有 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* 语句来尝试匹配剩下的部分。

异常组的属性与方法 编辑

message 编辑

对应创建异常组时,使用的参数 msg

excs 编辑

对应创建异常组时,使用的参数 excs。它包含参数中所有异常组成的元组。

该属性为只读属性。

subgroup(condition) 编辑

返回一个当前异常组中、匹配异常类型 condition 的异常组成的子组。如果没有匹配的异常,则返回 None

子组将和原异常组有相同的 exception 信息。

split(condition) 编辑

返回一个有两个元素的元组。元组第一个元素即为 self.subgroup(condition) ,而第二个元素为剩余的异常组成的子组。

子组将和原异常组有相同的 exception 信息。

derive(excs) 编辑

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)