30.3.2. 尽量抛异常,避免返回None

编写工具函数(utility function)时,许多Python程序员都爱用None这个返回值来表示特殊情况。对于某些函数来说,这或许有几分道理。

例如,我们要编写一个辅助函数计算两数相除的结果。在除数是0的情况下,返回None似乎相当合理,因为这种除法的结果是没有意义的。

def careful_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None


x, y = 1, 0
result = careful_divide(x, y)
if result is None:
    print("Invalid inputs")

上面这种if语句,会把函数返回0时的情况,也当成函数返回None时那样来处理。这种写法经常出现在Python代码里,因此像careful_divide这样,用None来表示特殊状况的函数是很容易出错的。有两种办法可以减少这样的错误。

第一种办法是,利用二元组把计算结果分成两部分返回。

def careful_divide(a, b):
    try:
        return True, a / b
    except ZeroDivisionError:
        return False, None


x, y = 1, 0
success, result = careful_divide(x, y)
if not success:
    print("Invalid inputs")

第二种办法比刚才那种更好,那就是不采用None表示特例,而是向调用方抛出异常(Exception),让他自己去处理。

def careful_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        raise ValueError("Invalid inputs")


x, y = 5, 2

try:
    result = careful_divide(x, y)
except ValueError:
    print("Invalid inputs")
else:
    print(f"Result is {result:.1f}")

我们可以把有可能抛出的异常写在文档里面,并希望调用方能够根据这份文档适当地捕获相关的异常

def careful_divide(a, b):
    """
    Args:
        a:
        b:
        divide a/b
    Returns:
        ValueError: when the inputs cannot be divided.
    """
    try:
        return a / b
    except ZeroDivisionError:
        raise ValueError("Invalid inputs")


x, y = 5, 2

try:
    result = careful_divide(x, y)
except ValueError:
    print("Invalid inputs")
else:
    print(f"Result is {result:.1f}")

要点:

用返回值None表示特殊情况是很容易出错的,因为这样的值在条件表达式里面,没办法与0和空白字符串之类的值区分,这些值都相当于False。

用异常表示特殊的情况,而不要返回None。

让调用这个函数的程序根据文档里写的异常情况做出处理。

通过类型注解可以明确禁止函数返回None,即便在特殊情况下,它也不能返回这个值。