Section 199.1: 过度宽泛的 except 子句
异常功能强大,但一个过度宽泛的 except 子句可能会在一行代码中将所有优势都抹杀。
try:
res = get_result()
res = res[0]
log('got result: %r' % res)
except:
if not res:
res = ''
print('got exception')
这个例子展示了反模式的三个症状:
- 不带异常类型的
except(第 5 行)会捕获所有异常,包括KeyboardInterrupt。这在某些情况下会阻止程序退出。 except块没有重新抛出错误,这意味着我们无法判断异常是来自get_result内部,还是因为res是一个空列表。- 最糟糕的是,如果我们担心结果为空,我们已经造成了更严重的问题。如果
get_result失败,res将完全未设置,而except块中对res的引用将引发NameError,完全掩盖了原始错误。
始终考虑您要处理的异常类型。阅读异常页面,了解基本异常的存在。
以下是上述例子的修正版本:
import traceback
try:
res = get_result()
except Exception:
log_exception(traceback.format_exc())
raise
try:
res = res[0]
except IndexError:
res = ''
log('got result: %r' % res)
我们捕获更具体的异常,并在必要时重新抛出。虽然多了一些行,但正确性提高了无数倍。
Section 199.2: 在调用计算密集型函数前先检查
程序很容易通过多次调用计算密集型函数而浪费时间。
例如,考虑这样一个函数:如果输入值可以产生一个整数,则返回一个整数,否则返回 None:
def intensive_f(value): # int -> Optional[int]
# complex, and time-consuming code
if process_has_failed:
return None
return integer_output
它可以这样使用:
x = 5
if intensive_f(x) is not None:
print(intensive_f(x) / 2)
else:
print(x, "could not be processed")
print(x)
虽然这可以工作,但它存在调用两次 intensive_f 的问题,这将使代码运行时间翻倍。一个更好的解决方案是提前获取函数的返回值。
x = 5
result = intensive_f(x)
if result is not None:
print(result / 2)
else:
print(x, "could not be processed")
然而,一个更清晰且可能更符合 Python 风格的方法是使用异常,例如:
x = 5
try:
print(intensive_f(x) / 2)
except TypeError: # The exception raised if None + 1 is attempted
print(x, "could not be processed")
这里不需要临时变量。在许多情况下,使用 assert 语句并捕获 AssertionError 可能会更好。
字典键
一个常见的例子是访问字典键。例如比较:
bird_speeds = get_very_long_dictionary()
if "european swallow" in bird_speeds:
speed = bird_speeds["european swallow"]
else:
speed = input("What is the air-speed velocity of an unladen swallow?")
print(speed)
与:
bird_speeds = get_very_long_dictionary()
try:
speed = bird_speeds["european swallow"]
except KeyError:
speed = input("What is the air-speed velocity of an unladen swallow?")
print(speed)
第一个例子需要两次遍历字典,由于这是一个很长的字典,每次遍历可能都需要很长时间。第二个例子只需要一次遍历字典,从而节省了大量的处理器时间。
另一种选择是使用 dict.get(key, default),但许多情况可能需要在键不存在时执行更复杂的操作。