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)