【问题标题】:how to improve exception handling in python/django如何改进 python/django 中的异常处理
【发布时间】:2020-07-30 23:14:59
【问题描述】:

这是我在 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,因此我需要捕获每个请求错误,这看起来更像是一个丑陋的摆弄......

【问题讨论】:

  • 好吧,这可能是stackoverflow.com/questions/38084360/…的副本,建议在if type(e).__name__ == 'ReadError'的函数中处理,这真的是pythonic/django的方式吗?
  • 这不是pythonic。通常使用__dunder__ 方法是最后的手段/解决方法,特别是检查类名非常hacky。试试using isinstance instead

标签: python django exception


【解决方案1】:

这是decorators 的用例。如果它是适用于所有视图的更通用的东西(例如,错误日志记录),您可以使用Django exception middleware hook,但这里似乎并非如此。

关于重复错误字符串的问题,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 is a subclass of RequestException in the requests library。我们也可以用异常类作为键来做一个字典,但是这里的问题是这不会处理异常类继承,这是一种稍后会产生细微错误的遗漏。 isinstance 函数是一种更可靠的检查方式。

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

那么在你看来:

from .decorators import handle_view_exception

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

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

【讨论】:

  • 这其实是一个很好的解决方案。尊重 DRY 原则,明确划分类职责。认为它应该被接受。
  • 我对此不太相信,实际上我正在寻找更通用的解决方案,所以我也会看看process_exception(),所以我希望我可以根据我的需要调整你的解决方案。而且因为目前似乎还没有更好的解决方案,所以我会接受它
  • @Asara 以何种方式不涵盖您的用例?
【解决方案2】:

你能有这样的东西吗:

views.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 ...的重复

你可以这样做:

views.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)

【讨论】:

  • 不过,这将需要在视图方法中放入大量错误处理代码。我的看法是,如果发生异常错误,我应该立即离开此方法,因为出现问题并且此方法不再负责处理它
猜你喜欢
  • 1970-01-01
  • 2017-06-28
  • 1970-01-01
  • 2017-10-16
  • 1970-01-01
  • 2021-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多