有两个问题合并成一个:
我应该多久使用自定义异常(以免过度使用)? 和
我是否应该优先选择自定义异常(而不是内置异常)? 让我们回答这两个问题。
过度使用自定义异常
你链接的 Dan Bader 的博客文章是一个很好的例子,说明了如何不应该做。这是自定义异常的滥用示例。每个异常类应该涵盖 一组相关的用途 (例如 ConfigError、BrowserError、DateParserError)。绝对不应该为每个需要引发异常的特定情况创建新的自定义异常。这就是异常消息的作用。
自定义 vs. 内置异常
这是一个更基于观点的话题,它也高度取决于特定的代码情境。我将展示两个有趣的例子(可能还有许多其他例子),在这些例子中,我认为使用自定义异常会更有益处。
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更改为其他库,那么你就必须修改所有捕获异常的模块。另一种捕获一些通用异常的替代方法也被
不建议使用。
如果在你的网络浏览器模块中创建自定义异常:
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类,因此您无需在Web浏览器模块中导入底层的urllib包来捕获它引发的异常。
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
,这种情况就不会发生。
RSAKeyAuthenticationFailedError
创建一个名为AuthError
的子类,以便使开发人员在调试时更容易理解错误消息。 - StardustGogeta