Python Cookbook中表达式异常处理详解(4.22章节)

任务

想写一个表达式,所以你无法直接用 ty/except语句,但你仍需要处理表达式可能抛出的异常。

解决方案

为了抓住异常,try/except是必不可少的,但ty/except是一条语句,在表达式内部使用它的唯一方法是借助一个辅助函数:

def throws(t,f,*a,**k):
'''如果f(*a,**k)抛出一个异常且其类型是t的话则返回True
(或者,如果t是一个元组的话,类型是t中的某项)'''
try:
	f(*a, **k)
except t:
	return True
else:
	return False

举个例子,假设你有一个文本文件,每行有一个数字,但文件也可能有多余的内容如空格行及注释行等。可以生成一个包含文件中的所有数字的列表,只需略去那些不是数字的行即可:

data = [float(line) for line in open(some_file) if not throws(ValueError,float,line)]

讨论

你可能会喜欢将函数命名为raises,但我个人更喜欢 throws,可能是出于对 C++的感情。不过不管什么名字,这个辅助函数都接受一个异常类型t作为第一个参数,接着是一个可调用体f,然后是任意的基于位置的参数a和命名参数k,两者都将被传递给f。像 if not throws(ValueError,float(line))这样的写法是不行的。当你调用函数时,Python在将控制权交给函数之前会对参数求值,如果参数的求值引发了异常,函数永远不会得到机会执行。这种情况,在很多人刚开始用Python标准库的unittest.TestCase类的assertRaises方法时屡有发生,我见过不止一次。

当 throws 函数执行时,它在 try/except 语句的 try 子句中调用f, 将那个任意的基于位置的参数和命名参数传递给f。如果在try子句中对f的调用引发了异常,且异常的类型是t(或者是列出的异常类型中的一种,如果t是一个异常类型的元组的话),则控制权交给了对应的 except 子句,在此例中,返回true 作为 throws 的结果。如果 ty 子句中没有异常发生,控制权会交给对应的else子句(如果有),它将返回false 作为 throws的结果。

注意,如果有什么非预期的异常(类型不在t的指定范围中),throws函数并不会尝试截获异常,因而 throws 就将被终止,异常则交给它的调用者。这是一个有意为之的设计。在 except 子句中用太大的网去捕获异常并不是什么好主意,那常常意味着查错查得头昏脑胀。如果调用者真的想要trows截获所有的异常,它可以这样调用:throws(Exception,…… 然后,就等着头疼吧。

throws 函数的问题是,实际上做了两次关键操作:一次是看它有没有抛出异常,先把结果抛诸脑后,另一次是获得结果。所以最好的结局是同时获得结果和被截获异常的提示。我刚开始是这么做的:

def throws(t,f,*a,**k):
#如果f(*a,**k)抛出异常且异常类型为t,返回(True,None)或者(False,x),其中x是f(*a,**k)的结果
try:
	return False, f(*a,**k)
except t:
	return True,None

不幸的是,这个版本不符合列表推导的要求,没有什么优雅的办法能够同时得到标志和结果。因此,我选择了一个不同的方法:一个在任何情况下都返回list的函数–如果有异常被捕获就返回空列表,否则就返回仅包含结果的列表。这个方法工作得很好,但是为了清晰起见,最好把函数名改一改:

def returns(t,f,*a,**k):
#正常情况下返回[f(*a,**k)],若有异常返回[]
try:
	return [f(*a,**k)]
except t:
	return []

最后生成的列表推导变得更加优雅,比解决方案中的版本好多了,至少我这么认为。

data = [x for line in open(some_file)
for x in returns(ValueError,float,line)]

作者:我不会编程555

物联沃分享整理
物联沃-IOTWORD物联网 » Python Cookbook中表达式异常处理详解(4.22章节)

发表回复