返回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是比较常用的一个方法。这样调用方就能够合理地按照函数中的说明文档来处理由此而引发的异常了。`

results matching ""

    No results matching ""