繁体   English   中英

如何改进 python/django 中的异常处理

[英]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_MAPexcept ()扩展为您希望捕获的任何其他已知异常类型吗?

如果您想减少"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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM