Skip to content

高效Python90条之第20条 遇到意外状况时应该抛出异常,不要返回None

要点

  • 用返回值None表示特殊情况是很容易出错的,因为这样的值在条件表达式里面,没办法与0和空白字符串之类的值区分,这些值都相当于False。
  • 用异常表示特殊的情况,而不要返回None。让调用这个函数的程序根据文档里写的异常情况做出处理。
  • 通过类型注解可以明确禁止函数返回None,即便在特殊情况下,它也不能返回这个值。

在 Python 中,使用 None 来表示特殊情况是一种常见的做法,但这种方法容易导致错误,因为 None 在条件表达式中被视为 False,这可能会与其他假值(如 0"" 等)混淆。为了避免这些问题,建议在遇到意外状况时抛出异常,而不是返回 None。此外,通过类型注解可以明确禁止函数返回 None,从而提高代码的可读性和类型安全性。

示例 1:返回 None 的问题。

假设一个函数 careful_divide,除数为 0 时返回 None

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


if __name__ == "__main__":
    for i, j in [(0, 2), (10, 0)]:
        result = careful_divide(i, j)
        print(f"Result: {result}")

# 输出:
# Result: 0.0
# Result: None

我们也可以自定义除数为 0 时的处理方式,比如 result is None

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


if __name__ == "__main__":
    for i, j in [(0, 10), (10, 0)]:
        result = careful_divide(i, j)
        if result is None:
            print("Division by zero occurred.")
        else:
            print(f"Result: {result}")
# 输出:
# Result: 0.0
# Division by zero occurred.

但是当我们把 result is None 写成 not result,就会发现问题,处理逻辑并没有按我们的预想执行。

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


if __name__ == "__main__":
    for i, j in [(0, 10), (10, 0)]:
        result = careful_divide(i, j)
        if not result:
            print("Division by zero occurred.")
        else:
            print(f"Result: {result}")
# 输出:
# Division by zero occurred.
# Division by zero occurred.

保留 not result 这种常用写法,我们进行如下代码改动,利用二元组把计算结果分成两部分返回​。元组的首个元素表示操作是否成功,第二个元素表示计算的实际值。这样写,会促使函数调用方去拆分返回值,比如 success, result = careful_divide(i, j),调用方可以先看看这次运算是否成功,比如检查 success,然后再决定怎么处理运算结果。

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


if __name__ == "__main__":
    for i, j in [(0, 10), (10, 0)]:
        success, result = careful_divide(i, j)
        if not success:
            print("Division by zero occurred.")
        else:
            print(f"Result: {result}")
# 输出:
# Result: 0.0
# Division by zero occurred.

但是,有些函数调用方会有可能忽略返回元组的第一个部分,例如 _, result = careful_divide(i, j),最合适的做法,就是向调用方抛出异常,让调用方自己决定怎么处理​。

def careful_divide(a, b):
    try:
        return True, a / b
    except ZeroDivisionError:
        raise ValueError("Division by zero occurred.") # 向调用方抛出异常


if __name__ == "__main__":
    for i, j in [(0, 10), (10, 0)]:
        # 让调用方自己决定如何处理异常
        try:
            _, result = careful_divide(i, j)
        except ValueError as e:
            print(e)
        else:
            print(f"Result: {result}")
# 输出:
# Result: 0.0
# Division by zero occurred.

更进一步,我们在给调用方抛出异常的基础上,通过类型注解来明确禁止函数返回 None。这样写,输入、输出与异常都显得很清晰,调用方出错的概率就变得很小了。

def careful_divide(a: float, b:float) -> float:
    try:
        return True, a / b
    except ZeroDivisionError:
        raise ValueError("Division by zero occurred.")


if __name__ == "__main__":
    for i, j in [(0, 10), (10, 0)]:
        try:
            _, result = careful_divide(i, j)
        except ValueError as e:
            print(e)
        else:
            print(f"Result: {result}")
# 输出:
# Result: 0.0
# Division by zero occurred.