首页 > 编程笔记

Python自定义异常(附带示例)

程序在运行的过程中可能会因为权限问题、用户输入问题、第三方 API 问题、请求参数缺失等问题产生异常,我们可以在程序内部通过捕获异常的语句让程序继续执行,也可以选择通过 raise 语句抛出异常让程序终止运行,如果不进行异常处理,异常最终会被 Python 解释器捕获并终止运行程序。

一般情况下,程序无法处理正常的逻辑执行过程时会发生异常。为了处理程序在运行过程中的异常和错误,Python 定义了很多的标准异常和异常处理机制来处理程序运行过程中出现的异常。

在高级编程语言中,一般都有错误和异常的概念,异常是可以捕获并被处理的,但是错误是不能被捕获的。一个健壮的程序,应尽可能地避免错误,捕获和处理各种异常。

Python 有两种错误很容易辨认:语法错误和异常。

Python 的语法错误(或称解析错误)是解析代码时出现的错误。当代码不符合 Python 语法规则时,Python 解释器在解析时就会报出 SyntaxError 语法错误,同时还会明确指出探测到错误的语句和报错原因,如少了冒号、混用中英文符号等。

即便 Python 程序的语法是正确的,在程序运行时,也有可能发生错误,运行期间检测到的错误被称为异常。大多数的异常都不会被程序处理,都以错误信息的形式展现,如除数为 0、年龄为负数、数组下标越界等。

异常处理在任何一门编程语言里都是值得关注的话题,良好的异常处理可以让程序更加健壮,清晰的错误信息能帮助开发人员快速修复问题。

Python 异常处理方法

默认情况下,程序发生异常是会终止的,如果我们要避免程序终止执行,可以使用捕获异常的方式获取这个异常的名称,再通过其他的逻辑代码让程序继续运行,这种处理叫作异常处理。

开发人员可以使用异常处理全面地控制自己的程序。异常处理大大提高了程序的健壮性和人机交互的友好性。

在 Python 语言中,处理异常的关键字主要有 try、except、else、finally 和 raise:
处理异常的关键字通常组合使用,不同的组合能实现不同的异常处理场景。合理的异常处理不仅能完善程序运行过程中的逻辑,也能提升程序运行的性能。语法如下:
try:
    <语句>  # 待捕获异常的代码
except <异常类>:
    <语句>  # 捕获某种类型的异常
except <异常类> as <变量名>:
    <语句>  # 捕获某种类型的异常并获得对象
else:
    <语句>  # 如果没有异常发生,则执行
finally:
    <语句>  # 退出try时总会执行,不管是否发生了异常,都要执行finally的部分
上述语法的具体含义如下:
一个 try 语句可以对应多个 expect 子句,但只能对应一个 finally 子句。我们可以使用 try-expect 组合检测和处理异常,也可以添加一个可选的 else 子句处理没有检测到异常的执行代码。而 finally 子句只用于检测异常和做一些必要的清除工作,例如无论是否发生异常,都要关闭连接。

Python 自定义异常

用户自定义异常,只要自定义异常类继承了 Exception 类即可。在自定义异常类时,我们基本不需要书写很多的代码,表1所示是 Python 中所有标准异常类。

Python 用异常对象来表示异常,当遇到错误,会触发异常。如果异常对象未被捕捉或处理,程序就会被终止执行。

表1:Python 中所有标准异常类
常见异常名称 描述
BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行,捕获用户中断行为,通常为按Ctrl + C键
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
SystemExit Python解释器请求退出
OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零(所有数据类型)
AssertionError 断言语句失败
AttributeError 对象没有这个属性
IOError 输入/输出操作失败
ImportError 导入失败
IndexError 序列中没有这个索引
KeyError 映射中没有这个键
MemoryError 内存溢出错误
NameError 未声明/初始化对象(没有属性)
UnboundLocalError 访问未初始化的本地变量
RuntimeError 一般的运行时错误
NotImplementedError 尚未实现的方法
SyntaxError Python语法错误
IndentationError 缩进错误
TabError Tab和空格混用
ValueError 传入无效的参数

如前文所述,Python 中自定义异常类型非常简单,只需要从 Exception 类继承即可。通过创建一个新的异常类,我们可以命名自己的异常。

在如下示例中我们创建了一个自定义异常类 Networkerror,基类为 BaseException,用于在异常触发时输出更多的信息:
class Networkerror(BaseException):
    def __init__(self,msg):
        self.msg=msg
    def __str__(self):
        return self.msg
try:
    raise Networkerror('类型错误')
except Networkerror as e:
    print(e)
在 try 中,若发生自定义异常,则执行 except 子句,其中变量 e 是用于创建 Networkerror 类的实例。

如果我们需要自主抛出一个异常,可以使用 raise 关键字抛出异常,如上述示例中的 raise Networkerror('类型错误')。

raise 的通常语法为:

raise异常类名称(描述信息)

在触发指定类型的异常的同时,附带异常的描述信息。

在实际调试程序的过程中,有时只获得异常的类型是远远不够的,还需要借助更详细的异常信息才能解决问题。捕获异常时,有两种方式可获得更多的异常信息,分别是:

TabError 的解决方法

Python 文件运行时报错:

TabError: inconsistent use of tabs and spaces in indentation

究其原因是 Python 文件中混用 Tab 和 Space 实现格式缩进,通常使用外部编辑器编辑 Python 文件时,会自动采用 Tab 进行格式缩进。

解决方法为将 Tab 转换成 4 个 Space。

示例

在进行 Web 开发的工程中,如果采用 Flask 框架,那么需要自定义异常处理类,方便统一进行异常的处理。此时自定义的异常处理类应该继承自 HTTPException,自定义的内容通常包含如下几点:
我们自定义异常类 APIException,返回的信息包括内部错误码、错误信息和请求的 URL 如代码清单1所示。

代码清单1:APIException
# -*- coding: utf-8 -*-
# @Time : 2022/7/11 4:21 下午
# @Project : ExceptionTestDemo
# @File : APIException.py
# @Version: Python3.9.8

from flask import request, json
from werkzeug.exceptions import HTTPException
 
class APIException(HTTPException):
    code = 500
    msg = 'sorry, we made a mistake!'
    error_code = 999
    def __init__(self, msg=None, code=None, error_code=None, headers=None):
        if code:
            self.code = code
        if error_code:
            self.error_code = error_code
        if msg:
            self.msg = msg
        super(APIException, self).__init__(msg, None)
    def get_body(self, environ=None):
        body = dict(
            msg=self.msg,
            error_code=self.error_code,
            request=request.method + ' ' + self.get_url_no_param()
        )
        text = json.dumps(body)
        return text
    def get_headers(self, environ=None):
        """Get a list of headers."""
        return [('Content-Type', 'application/json')]
    @staticmethod
    def get_url_no_param():
        full_path = str(request.full_path)
        main_path = full_path.split('?')
        return main_path[0]
有了 APIException 类,我们就可以自由地定义各种状态码以及对应的异常信息,然后在合适的位置抛出异常,如代码清单2所示。

代码清单2:APIExceptionExample
# -*- coding: utf-8 -*-
# @Time : 2022/7/11 5:21 下午
# @Project : ExceptionTestDemo
# @File : APIExceptionExample.py
# @Version: Python3.9.8

import APIException
 
class Success(APIException):
    code = 201
    msg = 'ok'
    error_code = 0
class ServerError(APIException):
    code = 500
    msg = 'sorry, we made a mistake!'
    error_code = 999
class ParameterException(APIException):
    code = 400
    msg = 'invalid parameter'
    error_code = 1000
class NotFound(APIException):
    code = 404
    msg = 'the resource are not found'
    error_code = 1001
自定义异常类可以帮助我们在需要的地方抛出异常。在发生异常时,我们可以对照状态码去查找对应的异常类,非常方便。

注意,虽然此处表述为异常类,但是我们可以在类中定义响应成功的返回,例如代码清单2中定义的状态码 201 对应的异常类,可以作为响应成功的返回。

推荐阅读