首页 > 解决方案 > 如何改进 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)

然后我当然可以改进错误消息。但总的来说,它是一种处理异常的pythonic方式if/else吗?例如RequestException,如果ConnectionError被抛出,我无法在这里捕获,所以我需要捕获每个请求错误,这看起来更像是一个丑陋的摆弄......

标签: pythondjangoexception

解决方案


这是装饰器的一个用例。如果它是适用于所有视图的更通用的东西(例如,错误日志记录),您可以使用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 的子类这一事实。我们也可以用异常类作为键来做一个字典,但这里的问题是它不能处理异常类继承,这是一种稍后会产生细微错误的遗漏。该isinstance函数是一种更可靠的检查方式。

如果您的异常树不断增长,您可以不断添加if语句。万一开始变得笨拙,我建议看这里,但我会说在错误处理中有这么多分支是一种代码味道。

那么在你看来:

from .decorators import handle_view_exception

@handle_view_exception
def boxinfo(request, url: str):
    box = get_box(url)
    box.connect()
    ...

这样,错误处理逻辑与您的视图完全分离,最重要的是,它是可重用的。


推荐阅读