![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=1739300402-uegvnojwepcopbcDdm3CVc5mosp7d2uF-0-363b8b1d78fb463f4dcdf7a0e37266f8)
调用这个函数时,可以按自己的方式处理这样的返回值。
![087-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/087-02.jpg?sign=1739300402-z4nmwh2yl23kSxkjip9Kb4wzTBMQgEpZ-0-cba1ef3f37496471504b5605766852bd)
如果传给careful_divide
函数的被除数为0,会怎么样呢?在这种情况下,只要除数不为0,函数返回的结果就应该是0。问题是,这个函数的返回值有时可能会用在if
条件语句里面,那时可能会根据值本身是否相当于False
来做判断,而不像刚才那样明确判断这个值是否为None
(第5条也列举了类似的例子)。
![087-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/087-03.jpg?sign=1739300402-w6XP3R7pFPU1LWkZdXL3WbVOOY8d34sm-0-2c2aabcc68a6a9b9d11d8220f69d3524)
上面这种if
语句,会把函数返回0时的情况,也当成函数返回None
时那样来处理。这种写法经常出现在Python代码里,因此像careful_divide
这样,用None
来表示特殊状况的函数是很容易出错的。有两种办法可以减少这样的错误。
第一种办法是,利用二元组把计算结果分成两部分返回(与这种写法有关的基础知识,参见第19条)。元组的首个元素表示操作是否成功,第二个元素表示计算的实际值。
![087-04](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/087-04.jpg?sign=1739300402-15zt6cCCd0Fh8b3dZ9OQO4Tu4nacQR73-0-fa333efecd8aeba9c26097c810d08258)
这样写,会促使调用函数者去拆分返回值,他可以先看看这次运算是否成功,然后再决定怎么处理运算结果。
![088-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/088-01.jpg?sign=1739300402-DI3Zd85qUfe26fgCqB9Jp6AbUOB7UWYQ-0-d65ff5fa1a1306d75ed2534c7ea65cc2)
但问题是,有些调用方总喜欢忽略返回元组的第一个部分(在Python代码里,习惯用下划线表示用不到的变量)。这样写成的代码,乍一看似乎没问题,但实际上还是无法区分返回0与返回None
这两种情况。
![088-02](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/088-02.jpg?sign=1739300402-HevnB5JjHlZP6b09tsqSYxh5lkncsxd5-0-0ad96f6968a6caa0493e16aa6c5ab34a)
第二种办法比刚才那种更好,那就是不采用None
表示特例,而是向调用方抛出异常(Exception
),让他自己去处理。下面我们把执行除法时发生的ZeroDivisionError
转化成ValueError
,告诉调用方输入的值不对(什么时候应该使用Exception
类的子类,可参见第87条)。
![088-03](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/088-03.jpg?sign=1739300402-T0QNSExxVlhSZqCKICoDmrF41SQssGUB-0-c895e29d87ea88a6a39efe9507319181)
现在,调用方拿到函数的返回值之后,不用先判断操作是否成功了。因为这次可以假设,只要能拿到返回值,就说明函数肯定顺利执行完了,所以只需要用try
把函数包起来并在else
块里处理运算结果就好(这种结构的详细用法,参见第65条)。
![088-04](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/088-04.jpg?sign=1739300402-urc1PG1YwsPw5yo5pTv19Il7DtvJWfdw-0-7e7b118063e85a69e17ac1f196ab9dff)
这个办法也可以扩展到那些使用类型注解的代码中(参见第90条),我们可以把函数的返回值指定为float
类型,这样它就不可能返回None
了。然而,Python采用的是动态类型与静态类型相搭配的gradual类型系统,我们不能在函数的接口上指定函数可能抛出哪些异常(有的编程语言支持这样的受检异常(checked exception),调用方必须应对这些异常)。所以,我们只好把有可能抛出的异常写在文档里面,并希望调用方能够根据这份文档适当地捕获相关的异常(参见第84条)。
下面我们给刚才那个函数加类型注解,并为它编写docstring。
![089-01](https://epubservercos.yuewen.com/873E3D/20818200901954706/epubprivate/OEBPS/Images/089-01.jpg?sign=1739300402-8JpTALEyzkE7fDFfrOAN9fx79TUg1uRz-0-d4cc1ada65fd1fc67f3a6fdef6d82293)
这样写,输入、输出与异常都显得很清晰,所以调用方出错的概率就变得很小了。
要点
- 用返回值
None
表示特殊情况是很容易出错的,因为这样的值在条件表达式里面,没办法与0和空白字符串之类的值区分,这些值都相当于False
。 - 用异常表示特殊的情况,而不要返回
None
。让调用这个函数的程序根据文档里写的异常情况做出处理。 - 通过类型注解可以明确禁止函数返回
None
,即便在特殊情况下,它也不能返回这个值。