首页 > 解决方案 > 我应该何时声明自定义异常?

问题描述

我想引发传达一些消息和与错误相关的值的异常。我想知道何时最适合声明自定义异常而不是使用内置异常。

我见过很多这样的例子还有更多类似的例子在其他网站上被推荐。

class NameTooShortError(ValueError):
    pass

def validate(name):
    if len(name) < 10:
        raise NameTooShortError(name)

我更倾向于编写代码,例如:

def validate(name):
    if len(name) < 10:
        raise ValueError(f"Name too short: {name}")

我的直觉是仅在需要将复杂或特定信息存储在异常实例中时才声明自定义异常。声明空班对我来说似乎是错误的。

标签: pythonexception

解决方案


有两个问题合并为一个:我应该多久使用一次自定义异常(不要过度使用它们)?并且我真的应该更喜欢自定义异常(而不是内置异常)吗?让我们两个都回答。

自定义异常过度使用

您链接的 Dan Bader 的博客文章是一个很好的例子,说明不应该这样做。过度使用自定义异常的示例。每个异常类都应涵盖一组相关用途(ConfigError、BrowserError、DateParserError)。您绝对不应该为需要提出某些问题的每个特定情况创建新的自定义异常。这就是异常消息的用途。

自定义与内置异常

这是一个更基于意见的主题,它也高度依赖于特定的代码场景。我将展示两个有趣的例子(可能很多),我认为使用自定义异常可能是有益的。

01:内部暴露

让我们创建一个简单的 Web 浏览器模块( Requests包的薄包装器):

import requests

def get(url):
    return requests.get(url)

现在假设您想在包中的多个模块中使用新的 Web 浏览器模块。在其中一些你想捕捉一些可能的网络相关异常:

import browser
import requests

try:
    browser.get(url)
except requests.RequestException:
    pass

此解决方案的缺点是您必须requests在每个模块中导入包才能捕获异常。此外,您还暴露了浏览器模块的内部。如果您决定将底层 HTTP 库从 Requests 更改为其他内容,则必须修改捕获异常的所有模块。捕获一些一般异常的替代方法也是不鼓励的。


如果您在 Web 浏览器模块中创建自定义异常:

import requests

class RequestException(requests.RequestException):
    pass

def get(url):
    try:
        return requests.get(url)
    except requests.RequestException:
        raise RequestException

那么您的所有模块现在都将避免上述缺点:

import browser

try:
    browser.get(url)
except browser.RequestException:
    pass

请注意,这也正是 Requests 包本身使用的方法——它定义了自己的RequestException类,因此您不必urllib为了捕获它引发的异常而在 Web 浏览器模块中导入底层包。

02:错误阴影

自定义异常不仅仅是为了让代码更漂亮。查看您的代码(稍作修改的版本)以发现一些非常邪恶的东西:

def validate(name, value):
    if len(name) < int(value):
        raise ValueError(f"Name too short: {name}")

    return name

现在有人将使用您的代码,但不是在短名称的情况下传播您的异常,他宁愿捕获它并提供默认名称:

name = 'Thomas Jefferson'

try:
    username = validate(name, '1O')
except ValueError:
    username = 'default user'

代码看起来不错,不是吗?现在请注意:如果您将name变量更改为字面上的任何字符串,该username变量将始终设置为'default user'. 如果您定义并引发了自定义异常ValidationError,则不会发生这种情况。


推荐阅读