返回exceptions而不是None
编写一个功效函数,对Python
程序员而言返回一个特殊的值为None
时,要有一个提取的过程。有些时候这会显得很有意义。例如:你需要一个关于两个数除法的工具函数,当发生除零状况时返回None
似乎显得很是自然而然,因为结果本身就是未定义的嘛。
def divide(a, b):
try:
return a/b
except ZeroDivisionError:
return None
因此,使用这个函数的代码就可以根据返回的结果来作进一步的解释处理了。
result = divide(x, y)
if result is None:
print('Invalid inputs!')
当分子为0
的时候会是什么样呢?这个函数将也会返回一个0
(当然咯,前提是分母非0
)。然而当你在类似于if
这样的条件语句进行评估的时候就有可能出现问题。因为你可能预期一个等价于False
的任何可能的值来表明条件能否得到执行,而不是预期一个None
值(详见第4项:编写功效函数而不是复杂的表达式)。
x, y = 0, 5
result = divide(x, y)
if not result:
print("Invalid inputs") # 很明显,这是错误的
当None
拥有一个特殊的含义的时候,Python
中的这种代码就会是一个很常见的错误了。这也是为什么从一个函数中返回None
容易使得代码出错的缘故。不过还好,我们有以下两种方法来减少此类事故的发生。
一是将返回值分割成一个二元组,元组中的第一个值代表本操作成功与否,而第二个参数就代表了对应于操作成功与否的真实的计算值了。
def divide(a, b): try: return True, a/b except ZeroDivisionError: return False, None
这样一来,调用了上面的
divide
函数的代码就需要解开返回的元组,这就要求调用方先考虑divide
函数的执行状态,而不是单纯的仅仅盯着除法的计算结果。success, result = divide(x, y) if not success: print("Invalid inputs")
虽然这样做很方便,但是却容易出现问题,那就是调用方很容易忽略
divide
函数返回的元组中的第一个状态值(在Python
中对于未使用的变量的惯例就是使用下划线变量名称,一个下划线即可)。再看divide
函数,乍一看返回的结果也没什么问题,但是事实上这和直接返回None
一样的糟糕。_, result = divide(x, y) if not result: print("Invalid inputs")
第二个方法也是更好一点的方法那就是永远不返回
None
值。相反,而是要触发,上抛给调用方一个异常,让调用方来处理。这里,我把一个ZeroDivisionError
以上变成了一个ValueError
异常来告知调用方,其输入值存在问题。def divide(a, b): try: return a/b except ZeroDivisionError as e: return ValueError("Invalid inputs") from e
现在,如果由于输入值存在问题而引发的异常就可以被调用方直接的处理掉了(这个行为应该被文档化;详见第49项:为每一个函数,类以及模块写文档信息)。而且调用方也不再需要单独的为返回值的状态多写一段代码了。如果函数并没有触发一个异常,那就说明咱们的输入值是正确的,异常处理的结果也变的如此清晰了。
x, y = 5, 2
try:
result = divide(x, y)
except ValueError:
print("Invalid inputs")
else:
print("Result is %.1f"% result)
>>>
Result is 2.5
备忘录
- 返回
None
的函数来作为特殊的含义是容易出错的,因为None
和其他的变量(例如zero
,空字符串)在条件表达式的判断情景下是等价的。 - 通过触发一个异常而不是直接的返回
None
是比较常用的一个方法。这样调用方就能够合理地按照函数中的说明文档来处理由此而引发的异常了。`