[英]how to improve exception handling in python/django
这是我在 django 项目中的异常处理示例:
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except requests.exceptions.ConnectionError as e:
context = {'error_message': 'Could not connect to your box because the host is unknown.'}
return render(request, 'box/error.html', context)
except requests.exceptions.RequestException as e:
context = {'error_message': 'Could not connect to your box because of an unknown error.'}
return render(request, 'box/error.html', context)
我可以通过以下方式解决它:
try:
box.connect()
except Exception as e:
return error_handling(request, e)
-
def error_handling(request, e):
if type(e).__name__ == requests.exceptions.ConnectionError.__name__:
context = {'error_message': 'Could not connect to your box because the host is unknown.'}
elif type(e).__name__ == requests.exceptions.RequestException.__name__:
context = {'error_message': 'Could not connect to your box because of an unknown error.'}
else:
context = {'error_message': 'There was an unkown error, sorry.'}
return render(request, 'box/error.html', context)
然后我当然可以改进错误消息。 但总的来说,它是一种用if/else
处理异常的pythonic方式吗? 例如,如果抛出ConnectionError
,我无法在此处捕获RequestException
,因此我需要捕获每个请求错误,这看起来更像是一个丑陋的摆弄......
这是装饰器的一个用例。 如果它是适用于所有视图的更通用的东西(例如,错误日志记录),您可以使用Django 异常中间件挂钩,但这里似乎并非如此。
关于重复错误字符串问题,Pythonic 的解决方法是插入一个常量基字符串,并插入{replaceable_parts}
,以便稍后您可以.format()
它们。
有了这个,假设我们有以下文件decorators.py
:
import functools
from django.shortcuts import render
from requests.exceptions import ConnectionError, RequestException
BASE_ERROR_MESSAGE = 'Could not connect to your box because {error_reason}'
def handle_view_exception(func):
"""Decorator for handling exceptions."""
@functools.wraps(func)
def wrapper(request, *args, **kwargs):
try:
response = func(request, *args, **kwargs)
except RequestException as e:
error_reason = 'of an unknown error.'
if isinstance(e, ConnectionError):
error_reason = 'the host is unknown.'
context = {
'error_message': BASE_ERROR_MESSAGE.format(error_reason=error_reason),
}
response = render(request, 'box/error.html', context)
return response
return wrapper
我们使用ConnectionError 是请求库中 RequestException 的子类这一事实。 我们也可以用异常类作为键来做一个字典,但这里的问题是这不会处理异常 class inheritance ,这是一种稍后会产生细微错误的遗漏。 isinstance
function 是执行此检查的更可靠方法。
如果您的异常树不断增长,您可以继续添加if
语句。 万一开始变得笨拙,我建议看这里,但我会说在错误处理中有这么多分支是一种代码味道。
那么在你看来:
from .decorators import handle_view_exception
@handle_view_exception
def boxinfo(request, url: str):
box = get_box(url)
box.connect()
...
这样,错误处理逻辑与您的视图完全分离,最重要的是,它是可重用的。
你能有这样的东西吗:
视图.py
EXCEPTION_MAP = {
ConnectionError: "Could not connect to your box because the host is unknown.",
RequestException: "Could not connect to your box because of an unknown error.",
}
UNKNOWN_EXCEPTION_MESSAGE = "Failed due to an unknown error."
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except (ConnectionError, RequestException) as e:
message = EXCEPTION_MAP.get(type(e)) or UNKNOWN_EXCEPTION_MESSAGE
context = {'error_message': message}
return render(request, 'box/error.html', context)
然后,您可以将EXCEPTION_MAP
和except ()
扩展为您希望捕获的任何其他已知异常类型吗?
如果您想减少"Could not connect to your box because...
你也许可以这样做:
视图.py
BASE_ERROR_STRING = "Could not connect to your box because {specific}"
EXCEPTION_MAP = {
ConnectionError: "the host is unknown.",
RequestException: "of an unknown error.",
}
UNKNOWN_EXCEPTION_MESSAGE = "Failed due to an unknown error."
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except (ConnectionError, RequestException) as e:
specific_message = EXCEPTION_MAP.get(type(e))
if specific_message:
message = BASE_ERROR_STRING.format(specific=specific_message)
else:
message = UNKNOWN_EXCEPTION_MESSAGE
context = {'error_message': message}
return render(request, 'box/error.html', context)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.