在Python的Web开发或者脚本开发中,全局异常捕获和统一异常处理是非常实用的功能,能够避免异常直接抛到上层导致程序中断,同时让所有异常响应都保持统一的结构,方便前端或者其他调用方处理。

为什么需要全局异常捕获和统一响应
如果没有统一的异常处理,不同的接口可能返回不同的错误格式,比如有的返回字符串,有的返回字典,有的直接抛出500错误,调用方需要适配多种格式,增加开发成本。同时零散的异常处理会导致大量重复的try-except代码,降低代码可读性。
全局异常捕获的作用就是拦截所有未被处理的异常,按照预设的规则进行处理,返回统一的标准格式响应,同时可以记录异常日志,方便后续排查问题。
基础全局异常捕获实现
Python内置的sys.excepthook可以用来设置全局的异常捕获钩子,当程序出现未捕获的异常时,会调用这个钩子函数。
import sys
import traceback
def global_exception_handler(exc_type, exc_value, exc_traceback):
# 如果是键盘中断,不处理
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
# 打印异常信息到控制台
print("捕获到全局异常:")
traceback.print_exception(exc_type, exc_value, exc_traceback)
# 这里可以添加日志上报逻辑
# 设置全局异常钩子
sys.excepthook = global_exception_handler
# 测试代码
def test_func():
return 1 / 0
test_func()
上面的代码设置了全局异常钩子,当出现未捕获的异常时,会先打印异常信息,你可以根据需求修改global_exception_handler函数,比如添加日志写入或者发送告警的逻辑。
Web框架中的统一异常处理拦截器
如果是使用Flask、Django等Web框架,框架本身提供了更方便的异常处理机制,不需要使用sys.excepthook。
Flask框架实现示例
Flask可以通过@app.errorhandler装饰器来注册全局的异常处理器,支持捕获指定类型的异常,也可以捕获所有未处理的异常。
首先定义标准响应格式,一般包含状态码、提示信息、数据、时间戳等字段:
from flask import Flask, jsonify, request
import traceback
import time
app = Flask(__name__)
# 定义标准响应格式
def standard_response(code, message, data=None):
return jsonify({
"code": code,
"message": message,
"data": data,
"timestamp": int(time.time())
})
# 捕获所有未处理的异常
@app.errorhandler(Exception)
def handle_global_exception(e):
# 打印异常堆栈
traceback.print_exc()
# 返回统一的标准错误响应
return standard_response(500, f"服务内部错误: {str(e)}"), 500
# 捕获404异常
@app.errorhandler(404)
def handle_404_exception(e):
return standard_response(404, "请求的资源不存在"), 404
# 自定义业务异常类
class BusinessException(Exception):
def __init__(self, code, message):
self.code = code
self.message = message
super().__init__(message)
# 捕获自定义业务异常
@app.errorhandler(BusinessException)
def handle_business_exception(e):
return standard_response(e.code, e.message), e.code
# 测试接口
@app.route("/divide", methods=["GET"])
def divide():
a = request.args.get("a", type=int)
b = request.args.get("b", type=int)
if b is None or b == 0:
raise BusinessException(400, "除数不能为0")
return standard_response(200, "操作成功", {"result": a / b})
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=False)
Django框架实现示例
Django可以通过自定义中间件或者在settings.py中配置EXCEPTION_HANDLER来实现全局异常处理,这里以DRF(Django REST Framework)为例,DRF提供了更完善的异常处理机制。
# 首先在settings.py中配置
REST_FRAMEWORK = {
"EXCEPTION_HANDLER": "utils.exception_handler.custom_exception_handler"
}
# 然后在utils/exception_handler.py中实现自定义处理器
from rest_framework.views import exception_handler
from rest_framework.response import Response
import traceback
import time
def custom_exception_handler(exc, context):
# 先调用DRF默认的异常处理器
response = exception_handler(exc, context)
# 获取异常堆栈信息
traceback.print_exc()
# 定义标准响应格式
def standard_response(code, message, data=None):
return {
"code": code,
"message": message,
"data": data,
"timestamp": int(time.time())
}
if response is not None:
# DRF已经处理的异常,比如权限错误、校验错误等
return Response(standard_response(response.status_code, str(response.data)))
else:
# 未处理的异常,返回500错误
return Response(standard_response(500, f"服务内部错误: {str(exc)}"), status=500)
标准格式响应的设计建议
标准响应格式建议包含以下字段,根据项目需求可以增减:
- code:业务状态码,比如200表示成功,400表示参数错误,500表示服务错误,和HTTP状态码可以分开,也可以保持一致
- message:提示信息,用于给调用方展示错误原因
- data:返回的数据内容,成功时返回业务数据,失败时可以为空
- timestamp:响应时间戳,方便调用方做时间相关的处理
如果是内部服务调用,还可以增加request_id字段,用来关联一次请求的日志,方便排查问题。
注意事项
1. 全局异常捕获不要吞掉所有异常,比如键盘中断、系统退出等异常应该正常抛出,避免影响程序正常退出。
2. 生产环境中不要在响应中返回详细的异常堆栈信息,避免泄露敏感信息,堆栈信息可以记录到日志中。
3. 自定义异常类要继承Exception,并且明确异常的状态码和提示信息,方便统一处理。
4. 如果是在异步程序中,比如使用asyncio,全局异常的处理方式会有所不同,需要额外处理事件循环中的未捕获异常。