编写高质量Python (第20条) 遇到意外状况时应该抛出异常,不要返回 None

第20条 遇到意外状况时应该抛出异常,不要返回 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("Invaild inputs")

​ 如果传给 careful_divide 函数的被除数为 0 ,会怎么样呢?在这种情况下,只要除数不为 0,函数返回的结果就应该是0.问题是,这个函数的返回值有时可能会用在 if 条件语句里面,那时可能会根据值本身是否相当于 False 来做判断,而不是像刚才那样明确判断这个值是否为 None(第 5 条 也列举了这样的一个例子)。

x, y = 0, 5
result = careful_divide(x, y)
if not result:
		print('Invaild inputs')
    
>>>
Invaild inputs

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

​ 第一种办法是,利用二元组把计算结果分成两部分来返回(参见 第19条)。元组的首个元素表示操作是否成功,第二个元素表示计算的实际值。

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

​ 这样写,会促使调用函数去拆分返回值,他可以先看看这次运算是否成功,然后再决定怎么处理运算结果。

success, result = careful_divide(x, y)
if not success:
	print('Invaild inputs')

​ 但问题是,有些调用方法总喜欢忽略返回元组的第一个部分(在 Python 代码里,习惯用下划线表示用不到的变量)。这样写成的代码,乍一看似乎没问题,但实际上还是无法区分返回 0 与 返回 None 这两种情况。

_, result = careful_divide(x, y)
if not result:
	print('Invaild inputs')

​ 第二种办法比刚才那种更好,那就是不采用 None 表示特例,而是向调用方法抛出异常(Exception),让他自己去处理。下面我们把执行除法时发生的 ZeroDivisionError 转化为 ValueError,告诉调用方输入的值不对(什么时候应该使用 Exception 的子类,参见 第87条)。

def careful_divide(a, b):
	try:
		return a / b
	except:
		raise ValueError('Invaild inputs')

​ 现在,调用方拿到函数的返回值之后,不再判断操作是否成功了。因为这次可以假设,只要能拿到返回值,就说明函数顺利肯定顺利执行完了,所以只需要用 try 把函数包起来,并在 else 块里面处理运算结果就好(这种结果的详细用法,参见 第65条)。

x, y = 5, 2
try:
	result = careful_divide(x, y)
except ValueError:
	print('Invaild Error')
else:
	print('Result is %.1f' % result)

>>>
Result is 2.5

​ 这个方法也可以扩展到那些使用类型注释的代码中(参见 第90条),我们可以把返回值指定为 float 类型,这样它就不可能返回 None。然而,Python 采用的是动态类型与静态类型相搭配的 graudal 类型系统,我们不能在函数的接口指定函数可能抛出哪些异常(有的编程语言支持这样的受检异常(checked exception),调用方必须应对这些异常)。所以,我们只好把有可能抛出的异常写在文档里面,并希望调用方能够根据这份文档适当地捕获相关的异常(参见 第84条)。

​ 下面我们给刚才那个函数加类型注解,并为它编写 docstring。

def careful_divide(a: float, b: float) -> float:
		'''Divides a by b
    
    Raises:
        ValueError: When the inputs cannot be divided. 
    '''
    try:
        return a / b 
    except ZeroDivisionError as e:
        raise ValueError('Invalid Error')
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值