普遍的共识是“不要使用例外!” 大部分来自其他语言,甚至有时已经过时。
在C ++中,由于“堆栈展开” ,抛出异常的代价非常高。每个局部变量声明都像with
Python中的语句,该变量中的对象可能运行析构函数。这些析构函数在引发异常时以及从函数返回时执行。这种“ RAII习惯用法”是不可或缺的语言功能,对于编写健壮,正确的代码非常重要-因此,RAII与廉价异常之间的平衡是C ++决定对RAII进行的权衡。
在早期的C ++中,许多代码不是以异常安全的方式编写的:除非您实际使用RAII,否则很容易泄漏内存和其他资源。因此,抛出异常会使该代码不正确。这不再合理,因为即使C ++标准库也使用异常:您不能假装不存在异常。但是,将C代码与C ++结合使用时,异常仍然是一个问题。
在Java中,每个异常都有一个关联的堆栈跟踪。堆栈跟踪在调试错误时非常有用,但是在从不打印异常的情况下会浪费很多精力,例如,因为它仅用于控制流程。
因此,在这些语言中,异常太昂贵而无法用作控制流。在Python中,这不再是一个问题,而且异常情况便宜很多。此外,Python语言已经遭受了一些开销,与其他控制流结构相比,这使得异常的代价不明显:例如,使用显式成员资格测试if key in the_dict: ...
来检查dict条目是否存在通常与访问该条目the_dict[key]; ...
并检查您是否进行检查一样快。得到一个KeyError。一些整体语言功能(例如生成器)是根据异常进行设计的。
因此,尽管没有技术上的理由专门避免使用Python中的异常,但仍然存在一个问题,即是否应使用它们而不是返回值。具有例外的设计级问题是:
一般而言,Python文化倾向于使用异常,但很容易过分习惯。想象一下一个list_contains(the_list, item)
函数,该函数检查列表是否包含与该项目相同的项目。如果通过绝对令人讨厌的异常传达结果,因为我们必须这样称呼它:
try:
list_contains(invited_guests, person_at_door)
except Found:
print("Oh, hello {}!".format(person_at_door))
except NotFound:
print("Who are you?")
返回布尔值会更清楚:
if list_contains(invited_guests, person_at_door):
print("Oh, hello {}!".format(person_at_door))
else:
print("Who are you?")
如果已经假定该函数返回一个值,则在特殊情况下返回一个特殊值很容易出错,因为人们会忘记检查该值(这可能是C语言中1/3问题的原因)。例外通常更正确。
一个很好的例子是一个pos = find_string(haystack, needle)
函数,该函数搜索needle
haystack字符串中字符串的第一个匹配项,然后返回起始位置。但是,如果他们的干草堆弦不包含针弦怎么办?
C并由Python模仿的解决方案是返回一个特殊值。在C中,这是一个空指针,在Python中,这是-1
。当将位置用作字符串索引而不进行检查时,这将导致令人惊讶的结果,尤其-1
是在Python中有效的索引时。在C语言中,您的NULL指针至少会给您一个段错误。
在PHP中,将返回不同类型的特殊值:布尔值FALSE
而不是整数。事实证明,由于语言的隐式转换规则,这实际上并没有任何改善(但请注意,在Python中,布尔值也可以用作整数!)。不返回一致类型的函数通常被认为非常混乱。
一个更健壮的变体是在找不到字符串时引发异常,这确保了在正常控制流程中不可能意外地使用特殊值代替普通值:
try:
pos = find_string(haystack, needle)
do_something_with(pos)
except NotFound:
...
另外,也可以使用总是返回不能直接使用但必须首先解开的类型,例如,结果布尔元组,其中布尔值指示是否发生了异常或结果是否可用。然后:
pos, ok = find_string(haystack, needle)
if not ok:
...
do_something_with(pos)
这迫使您立即处理问题,但是很快就会很烦人。它还会阻止您轻松链接功能。现在,每个函数调用都需要三行代码。Golang是一种认为这种滋扰值得安全的语言。
综上所述,异常并不是完全没有问题,可以明确地被过度使用,特别是当它们替换“正常”返回值时。但是,当用于表示特殊情况(不一定只是错误)时,异常可以帮助您开发干净,直观,易于使用且难以滥用的API。