68. drf之全局异常处理
1. 回顾
继承APIView的视图类,运行步骤:
1.去除csrf认证
2.包装新的request
3.运行:认证、权限、频率
三大组件运行完,才运行视图类的函数
4.统一处理了异常:三大组件、视图类的函数只要出现了异常,都会被统一处理
2. 引入
在前端开发中,为了便于处理后端报错,通常需要后端返回统一的格式。
比如根据错误码展示不同的提示信息给用户。
{code:999,msg:'系统异常,请联系系统管理员'}
// 其中code表示错误码,msg表示错误信息。
只要有三大组件,视图类的方法出了异常,都会运行一个函数:
# 全局异常处理
# 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
在后端开发中,无论是视图类的方法出现异常,还是其他地方出现异常,都可以通过全局异常处理来进行统一的处理。
例如,给定的Python代码片段展示了一个全局异常处理函数,它会处理所有视图类方法出现的异常。
通过配置'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
当发生异常时,系统会调用该函数进行异常处理。
在该函数中,可以执行一些自定义的操作来处理异常
比如记录日志、返回统一的错误响应等。
但是不能在源码中修改处理异常的函数
这是因为源码是框架或库的核心部分,更改源码可能导致意想不到的后果,并且对于源码的修改在升级或维护时可能会产生冲突。
因此,在开发过程中应尽量避免修改源码,而是通过配置或继承等方式来实现自定义的异常处理逻辑。
3. drf全局异常处理源码分析
查看APIView的源码,定位到rest_framework的views.py中
APIView类中有dispatch函数,可以通过views.py的结构找到
dispatch中如果遇到了异常,就会运行
except Exception as exc:
response = self.handle_exception(exc)
本质就是在运行APIView类里面的handle_exception函数
handle_exception的步骤:
exception_handler = self.get_exception_handler() # 1.获取drf提供的异常处理函数
context = self.get_exception_handler_context() # 2.获取context(上下文对象),这里是个字典,有当次请求的request和视图类的对象view
response = exception_handler(exc, context) # 3.运行异常处理函数,传入了exc异常对象和上下文对象
return response # 4.返回response对象给前端Response(data={}, headers={}),返回给前端的数据,取决于response对象中的data
查看以上步骤1中get_exception_handler的源码
def get_exception_handler(self):
return self.settings.EXCEPTION_HANDLER
get_exception_handler函数的本质是去rest_framework的settings.py(非项目的配置文件)中找key为EXCEPTION_HANDLER的值
# Exception handling
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
EXCEPTION_HANDLER的本质就是rest_framework包下views.py文件中的exception_handler函数
exception_handler函数的作用是一旦出现了异常,包装成Response对象,最终前端展示的数据取决于Response的data属性
查看以上步骤2中get_exception_handler_context的源码
def get_exception_handler_context(self):
return {
'view': self,
'args': getattr(self, 'args', ()),
'kwargs': getattr(self, 'kwargs', {}),
'request': getattr(self, 'request', None)
}
get_exception_handler_context的本质是视图函数上的参数
可以在app01的views.py中导入exception_handler,从而查看这个函数的源码
from rest_framework.views import exception_handler
def exception_handler(exc, context): if isinstance(exc, Http404): exc = exceptions.NotFound(*(exc.args)) elif isinstance(exc, PermissionDenied): exc = exceptions.PermissionDenied(*(exc.args))
# 所有drf的异常类,都继承自APIException,
# 通过from rest_framework.exceptions导入,包含ValidationError、PermissionDenied、AuthenticationFailed if isinstance(exc, exceptions.APIException): headers = {} if getattr(exc, 'auth_header', None): headers['WWW-Authenticate'] = exc.auth_header if getattr(exc, 'wait', None): headers['Retry-After'] = '%d' % exc.wait if isinstance(exc.detail, (list, dict)): data = exc.detail else: data = {'detail': exc.detail} set_rollback() return Response(data, status=exc.status_code, headers=headers) # 返回标准格式的错误,前端不会报错崩溃 return None # 如果错误类型不在rest_framework.exceptions中,返回NOne,前端会报错崩溃
app01的urls.py
from django.urls import path
from .views import BookView
urlpatterns = [
path('book/', BookView.as_view()),
]
app01的views.py
from rest_framework.views import APIView
class BookView(APIView):
def get(self, request, *args, **kwargs):
return None
报错原因为视图类不能返回None

视图类不返回None,而是返回标准格式,前端则不会报错崩溃
from rest_framework.views import APIView
class BookView(APIView):
def get(self, request, *args, **kwargs):
return Response({'code': 100, 'msg': '请求成功'})

(1)如果在视图类中抛出APIException类型的错误,则前端会展示APIException的detail形式的错误
from rest_framework.views import APIView
from rest_framework.exceptions import APIException
class BookView(APIView):
def get(self, request, *args, **kwargs):
raise APIException('some errors有些错误')

(2)如果在视图类中抛出python类型Exception的错误,则drf函数不会处理,以通过返回None的形式导致前端会报错崩溃
from rest_framework.views import APIView
class BookView(APIView):
def get(self, request, *args, **kwargs):
raise Exception('some errors有些错误')

总结:drf的异常处理函数exception_handler只处理了继承自rest_framework.exceptions的异常,没处理django或python的异常
为了使drf的异常处理函数exception_handler也能处理django或python的异常,需要重写这个函数,可以做到返回统一的格式
如果没有报错
查询所有:{code:100,msg:'成功',results:[{},{},]}
查询单条:{code:100,msg:'成功',result:{}}
删除:{code:100,msg:'删除成功'}
发送短信:{code:100,msg:'短信发送成功'}
如果有报错:
{code:101,msg:'发送短信失败,请稍后再试'}
{code:103,msg:'服务器异常,请联系系统管理员'}
4. drf全局异常处理介绍
4.1 概念
DRF 全局异常处理,用于拦截视图抛出的全部异常,统一格式化返回 JSON 响应,替代框架默认的 500、404 等原生页面输出。
核心入口为 DRF 的 exception_handler 函数,自定义异常处理器需要重写该逻辑,在项目配置文件中注册,分为 DRF 内置异常、Python 原生异常、自定义业务异常三类处理场景。
运行步骤:
请求到达视图 → 抛出异常 → 调用配置的 exception_handler → 优先执行 DRF 内置异常处理 → 内置处理返回 None 时,进入自定义扩展逻辑 → 返回标准化 Response 对象。
4.2 如何使用
1. 创建异常处理工具文件,例如app01的exception.py
2. 编写自定义异常处理函数
3. 在 settings.py 中配置 REST_FRAMEWORK,指定 EXCEPTION_HANDLER 指向自定义函数
4. 可选:定义业务自定义异常类,在视图中主动抛出
4.3 参数说明
| 参数 | 说明 |
|---|---|
| exc | 捕获到的异常实例对象,可读取 exc.detail 获取异常描述 |
| context | 上下文字典,包含 view、request 等对象,可拿到当前视图与请求信息 |
| response | 原生 exception_handler 返回结果,不为 None 代表 DRF 已经识别该异常;None 代表未识别,属于系统未知异常 |
| EXCEPTION_HANDLER | DRF 配置项,值为字符串,路径为 模块名。函数名 |
5. 重写异常处理函数
5.1 基础版
app01中定义exception.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
def extra_exception_handler(exc, context):
return Response({'code': 999, 'msg': '请求异常,等一下再试'})
项目配置文件settings.py中
REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'app01.exception.extra_exception_handler',}
drf的异常处理组件'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'已经由自定义的函数extra_exception_handler接管
所有异常都将由extra_exception_handler处理
(1)视图类中抛出APIException类型的错误,前端不再展示APIException的detail形式的错误,而是返回统一错误格式
from rest_framework.views import APIView
from rest_framework.exceptions import APIException
class BookView(APIView):
def get(self, request, *args, **kwargs):
raise APIException('some errors有些错误')

(2)视图类中抛出python类型Exception的错误,不再以drf函数返回None的形式导致前端会报错崩溃,而是返回统一错误格式
from rest_framework.views import APIView
class BookView(APIView):
def get(self, request, *args, **kwargs):
raise Exception('some errors有些错误')

(3)视图类中除以0的错误,不再以drf函数返回None的形式导致前端会报错崩溃,而是返回统一错误格式
from rest_framework.views import APIView
class BookView(APIView):
def get(self, request, *args, **kwargs):
1/0
return Response({'code': 100, 'msg': '请求成功'})

自定义的异常处理函数extra_exception_handler返回的都是统一错误格式,没有对异常类型加以区分,是drf异常还是python异常
5.2 区分异常类型
[1] 代码
在自定义的异常处理函数中,区分是drf的异常,还是python的异常
app01的exception.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
def extra_exception_handler(exc, context):
response = exception_handler(exc, context) # 按原来的handle_exception写法
# drf内置的exception_handler函数捕获到drf类型的异常会返回Response对象,Python类型异常exception_handler会直接返回None,不会生成response实例。
if response: # 有值,说明是drf异常
# 取出detail,返回给前端
# 判断response.data是列表还是字典
if isinstance(response.data, list):
msg = response.data[0]
elif isinstance(response.data, dict):
msg = response.data.get('detail', None) or 'drf的detail没有值,等一下再试' # 取不到detail的值时,or之后的字符串给None赋值
else:
msg = '未知错误'
return Response({'code': 666, 'msg': f'drf请求异常[{msg}]'})
else: # 没有值,是python的异常
msg = str(exc) # 从错误实例exc获取 调用异常实例的__str__魔术方法,提取异常的描述字符串。
return Response({'code': 999, 'msg': f'python请求异常[{msg}]'})
(1)视图类中抛出drf的APIException类型的错误,返回drf请求异常
from rest_framework.views import APIView
from rest_framework.exceptions import APIException
class BookView(APIView):
def get(self, request, *args, **kwargs):
raise APIException('some errors有些错误')

(2)视图类中抛出python类型Exception的错误,返回python请求异常
from rest_framework.views import APIView
class BookView(APIView):
def get(self, request, *args, **kwargs):
raise Exception('some errors有些错误')

(3)视图类中除以0的错误,返回python请求异常
from rest_framework.views import APIView
from rest_framework.response import Response
class BookView(APIView):
def get(self, request, *args, **kwargs):
1 / 0
return Response({'code': 100, 'msg': '请求成功'})

[2] 补充:response.data
(1) 概念
response.data 是 DRF 内置 exception_handler 组装出来的错误数据。
大部分 DRF 校验失败场景输出字典,少部分字段校验会输出列表,还存在字符串类型。
response.data 的类型,由抛出的 APIException 内部 detail 属性决定,也由序列化器校验报错的生成逻辑决定。
(2) 产生字典的场景
绝大多数 DRF 异常,response.data 为字典。
典型来源:
1. 主动抛出 APIException、AuthenticationFailed、PermissionDenied。detail 为字符串。
输出:{"detail":"错误文本"}
2. Django Http404、PermissionDenied,被 DRF 适配包装。
输出:{"detail":"Not found"}
示例 data:
{"detail":"认证失败"}
(3) 产生列表的场景
只出现在序列化器字段校验报错,序列化器校验失败抛出 ValidationError。
当序列化器对单个字段校验失败,ValidationError 的 detail 是字典,value 为列表。
exception_handler 处理 ValidationError 时,直接把 detail 赋值给 response.data。
{"username":["该用户名已存在"]}
(4) response.data 全部可能类型
| 类型 | 触发条件 | 示例 |
|---|---|---|
| dict | 绝大多数异常,APIException、404、权限拒绝、序列化器字段校验 | {"detail":"xxx"},{"age":["数字超出范围"]} |
| list | 手动 raise ValidationError (字符串列表) | ["参数 1 错误","参数 2 错误"] |
| str | 手动 raise ValidationError ("字符串"),detail 直接传字符串 | "输入格式错误" |
(5) rest_framework.views.exception_handler 源码片段逻辑简化
1. 如果异常是 APIException,取 exc.detail,赋值给 response.data。
2. detail 可以是字符串、列表、字典,完全由抛异常时传入的值决定。
3. ValidationError 继承 APIException,序列化器校验失败会构造 ValidationError,detail 结构随校验结果变化。
5.3 将错误码分得更细
[1] 代码
app01的exception.py
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError, Throttled, AuthenticationFailed
def extra_exception_handler(exc, context): # DRF原生异常处理器接收两个参数,即使没有用到context也必须写,否则报错
if isinstance(exc, ValidationError):
data = {'code': 101, 'msg': f'字段校验失败'}
elif isinstance(exc, AuthenticationFailed):
data = {'code': 102, 'msg': '登录认证失败'}
elif isinstance(exc, Throttled):
data = {'code': 103, 'msg': '超过频率限制'}
else:
data = {'code': 666, 'msg': str(exc)} # 调用异常实例的__str__魔术方法,提取异常的描述字符串。
return Response(data=data)
[2] 原理
定义自定义异常处理函数,DRF 全局异常处理器固定要求两个入参。
exc:实际捕获到的异常实例对象,用来判断是什么类型的错误。
context:上下文字典,包含当前 request、view 视图类对象等运行环境信息,本示例没有使用该参数。
isinstance 判断捕获到的异常实例,是否属于 ValidationError 类型。
原生 ValidationError 本身携带详细字段错误信息,本代码直接覆盖,丢弃原本的字段错误详情。
组装自定义返回字典。code 为业务错误码,msg 对外展示错误提示文本。
把组装好的字典传入 Response,作为接口返回值。
项目的配置文件中指定该函数作为全局异常处理函数。REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'app01.exception.extra_exception_handler',}
[3] 运行链路简述
1. 接口请求进入 APIView 视图运行。
2. 视图执行过程抛出异常。
3. APIView 顶层捕获异常,读取 REST_FRAMEWORK 的 EXCEPTION_HANDLER 配置。
4. 调用 extra_exception_handler,传入 exc 异常对象、context 上下文。
5. 函数内部判断异常类型,组装自定义 data,返回 Response 实例。
6. Django 将该 Response 转为 HTTP JSON 响应返回客户端。
[4] 测试
视图类中抛出drf的AuthenticationFailed类型的错误
app01的views.py
from rest_framework.views import APIView
from rest_framework.exceptions import ValidationError, AuthenticationFailed
class BookView(APIView):
def get(self, request, *args, **kwargs):
raise AuthenticationFailed('some errors有些错误')
原生AuthenticationFailed本身具有详细字段错误信息,自定义全局异常处理函数直接覆盖,丢弃原本的字段错误详情('some errors有些错误')。

如果需要使用原生AuthenticationFailed的字段错误信息,通过exc.detail获取
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError, Throttled, AuthenticationFailed
def extra_exception_handler(exc, context): # DRF原生异常处理器接收两个参数,即使没有用到context也必须写,否则报错
if isinstance(exc, ValidationError):
data = {'code': 101, 'msg': exc.detail}
elif isinstance(exc, AuthenticationFailed):
data = {'code': 102, 'msg': exc.detail}
elif isinstance(exc, Throttled):
data = {'code': 103, 'msg': exc.detail}
else:
data = {'code': 666, 'msg': str(exc)} # 调用异常实例的__str__魔术方法,提取异常的描述字符串。
return Response(data=data)

[5] APIException报错测试与记录日志
app01的exception.py
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError, Throttled, AuthenticationFailed, APIException
import time
def extra_exception_handler(exc, context): # DRF原生异常处理器接收两个参数,即使没有用到context也必须写,否则报错
if isinstance(exc, ValidationError):
data = {'code': 101, 'msg': exc.detail}
elif isinstance(exc, AuthenticationFailed):
data = {'code': 102, 'msg': exc.detail}
elif isinstance(exc, Throttled):
data = {'code': 103, 'msg': exc.detail}
elif isinstance(exc, ZeroDivisionError):
data = {'code': 104, 'msg': '不能除以0'}
elif isinstance(exc, APIException):
data = {'code': 105, 'msg': exc.detail}
else:
data = {'code': 666, 'msg': str(exc)} # 调用异常实例的__str__魔术方法,提取异常的描述字符串。
# 运行到这里,说明出现异常了,需要记录日志
request = context.get('request')
view = context.get('view')
print(f"""运行异常:
错误原因是:[{str(exc)}]
时间是:[{time.strftime('%Y-%m-%d %H:%M:%S')}]
请求地址是:[{request.get_full_path()}]
请求方式是:[{request.method}]
用户是:[{request.user.username or '未登录用户'}]
出错的视图类是:[{str(view)}]
""")
return Response(data=data)
app01的views.py
from rest_framework.response import Response
from rest_framework.views import APIView
class BookView(APIView):
def get(self, request, *args, **kwargs):
return Response({'code': 999, 'msg': '请求成功'})
当发送post请求时,触发APIException报错

打印日志

6. isinstance与issubclass
isinstance()函数和issubclass()函数是Python中的两个内置函数,用于判断对象与类之间的关系。
[1] isinstance
isinstance()函数用于检查一个对象是否是一个类的实例。
它接受两个参数
第一个参数是待检查的对象
第二个参数是类或类型。
如果对象是第二个参数指定的类或类型的实例
则返回True;
否则返回False。
例如:
class Person:
pass
person = Person()
print(isinstance(person, Person)) # 输出:True
print(isinstance(person, object)) # 输出:True
print(isinstance(person, str)) # 输出:False
在这个例子中,通过isinstance()函数可以判断person对象是否是Person类的实例。
同时,由于所有的类都继承自object类,所以person对象也被认为是object类的实例。
[2] issubclass
issubclass()函数用于检查一个类是否是另一个类的子类。
它接受两个参数
第一个参数是待检查的类
第二个参数是基类或超类。
如果第一个参数是第二个参数指定的类或类型的子类
则返回True;
否则返回False。
例如:
class Animal:
pass
class Dog(Animal):
pass
print(issubclass(Dog, Animal)) # 输出:True
print(issubclass(Dog, object)) # 输出:True
print(issubclass(Dog, str)) # 输出:False
在这个例子中,通过issubclass()函数可以判断Dog类是否是Animal类的子类。
同时由于所有的类都是object类的子类,所以Dog类也被认为是object类的子类。
这就是isinstance()函数和issubclass()函数的详解。
通过使用这两个函数,我们可以判断对象与类之间的关系,从而进行相应的处理。

浙公网安备 33010602011771号