![Effective Python:编写高质量Python代码的90个有效方法(原书第2版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/417/39980417/b_39980417.jpg)
第20条 遇到意外状况时应该抛出异常,不要返回None
编写工具函数(utility function)时,许多Python程序员都爱用None
这个返回值来表示特殊情况。对于某些函数来说,这或许有几分道理。例如,我们要编写一个辅助函数计算两数相除的结果。在除数是0的情况下,返回None
似乎相当合理,因为这种除法的结果是没有意义的。
![087-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/087-01.jpg?sign=1739254186-y4PSK1pR3bxWLacjFo8vASzuOvkNUSmX-0-119bb3438b32fb1e57b0c4d662dcdd3a)
调用这个函数时,可以按自己的方式处理这样的返回值。
![087-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/087-02.jpg?sign=1739254186-oU8liCwxKtlepT8ySDriXwOwLeZ5swxU-0-cbb234f1d91a4c08666db0735bc333d1)
如果传给careful_divide
函数的被除数为0,会怎么样呢?在这种情况下,只要除数不为0,函数返回的结果就应该是0。问题是,这个函数的返回值有时可能会用在if
条件语句里面,那时可能会根据值本身是否相当于False
来做判断,而不像刚才那样明确判断这个值是否为None
(第5条也列举了类似的例子)。
![087-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/087-03.jpg?sign=1739254186-Zetvzt9VuPvYir3v1RJ79924yJXTjdR1-0-4eb448f805bca42fa9ef4ab1e58d8822)
上面这种if
语句,会把函数返回0时的情况,也当成函数返回None
时那样来处理。这种写法经常出现在Python代码里,因此像careful_divide
这样,用None
来表示特殊状况的函数是很容易出错的。有两种办法可以减少这样的错误。
第一种办法是,利用二元组把计算结果分成两部分返回(与这种写法有关的基础知识,参见第19条)。元组的首个元素表示操作是否成功,第二个元素表示计算的实际值。
![087-04](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/087-04.jpg?sign=1739254186-kLaqKkglbwPyAfJGrvkAlC2eUwamqq7Y-0-302e909d949d40e92ad121ee83b96c9d)
这样写,会促使调用函数者去拆分返回值,他可以先看看这次运算是否成功,然后再决定怎么处理运算结果。
![088-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/088-01.jpg?sign=1739254186-3vbWmMcw0vbeY6Lq6mQHb11BUuzbVROJ-0-25e53a74218c663395793efebe4ba137)
但问题是,有些调用方总喜欢忽略返回元组的第一个部分(在Python代码里,习惯用下划线表示用不到的变量)。这样写成的代码,乍一看似乎没问题,但实际上还是无法区分返回0与返回None
这两种情况。
![088-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/088-02.jpg?sign=1739254186-ft5zfISQZrmZlAO2VpZsBQjVyZHBfrDy-0-64a9075d511e153e3086a49e883b38df)
第二种办法比刚才那种更好,那就是不采用None
表示特例,而是向调用方抛出异常(Exception
),让他自己去处理。下面我们把执行除法时发生的ZeroDivisionError
转化成ValueError
,告诉调用方输入的值不对(什么时候应该使用Exception
类的子类,可参见第87条)。
![088-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/088-03.jpg?sign=1739254186-wwLm38WOtWuakkxU4ls1A3GjBc7cDPNy-0-d1cabf96466c5d4c49eb1bff9462be94)
现在,调用方拿到函数的返回值之后,不用先判断操作是否成功了。因为这次可以假设,只要能拿到返回值,就说明函数肯定顺利执行完了,所以只需要用try
把函数包起来并在else
块里处理运算结果就好(这种结构的详细用法,参见第65条)。
![088-04](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/088-04.jpg?sign=1739254186-YzqoMIF8H7KlqYXADyDo2nVW1Ohi2f3u-0-010bf75e8aaaf4c6f0ff29ed0f4c7381)
这个办法也可以扩展到那些使用类型注解的代码中(参见第90条),我们可以把函数的返回值指定为float
类型,这样它就不可能返回None
了。然而,Python采用的是动态类型与静态类型相搭配的gradual类型系统,我们不能在函数的接口上指定函数可能抛出哪些异常(有的编程语言支持这样的受检异常(checked exception),调用方必须应对这些异常)。所以,我们只好把有可能抛出的异常写在文档里面,并希望调用方能够根据这份文档适当地捕获相关的异常(参见第84条)。
下面我们给刚才那个函数加类型注解,并为它编写docstring。
![089-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/089-01.jpg?sign=1739254186-LyyXlZX89JG5lw2wqokvWBb4fT7B4eie-0-cadf837c822637d37182f819f2b1a7f8)
这样写,输入、输出与异常都显得很清晰,所以调用方出错的概率就变得很小了。
要点
- 用返回值
None
表示特殊情况是很容易出错的,因为这样的值在条件表达式里面,没办法与0和空白字符串之类的值区分,这些值都相当于False
。 - 用异常表示特殊的情况,而不要返回
None
。让调用这个函数的程序根据文档里写的异常情况做出处理。 - 通过类型注解可以明确禁止函数返回
None
,即便在特殊情况下,它也不能返回这个值。