在Python中应该多常定义自定义异常?
在我尝试消除我写的一个用于监控特定工作流程的Python模块中的潜在竞争条件时,我了解到Python有一种编程风格叫做“宁可事后求情,也不要事先请求许可”(EAFP)。现在,我在使用try/except块时抛出了很多自定义异常,而以前我更倾向于使用if/then语句。
我对Python还很陌生,这种EAFP风格从逻辑上讲是有道理的,而且似乎让我的代码更健壮,但我总觉得这样做有点过头。每个方法定义一个或多个异常是不是不好的做法呢?
这些自定义异常通常只对单个方法有用,虽然这看起来是一个功能上正确的解决方案,但似乎需要维护的代码量有点大。
这里有一个示例方法:
class UploadTimeoutFileMissing(Exception):
def __init__(self, value):
self.parameter = value
def __str__(self):
return repr(self.parameter)
class UploadTimeoutTooSlow(Exception):
def __init__(self, value):
self.parameter = value
def __str__(self):
return repr(self.parameter)
def check_upload(file, timeout_seconds, max_age_seconds, min_age_seconds):
timeout = time.time() + timeout_seconds
## Check until file found or timeout
while (time.time() < timeout):
time.sleep(5)
try:
filetime = os.path.getmtime(file)
filesize = os.path.getsize(file)
except OSError:
print "File not found %s" % file
continue
fileage = time.time() - filetime
## Make sure file isn't pre-existing
if fileage > max_age_seconds:
print "File too old %s" % file
continue
## Make sure file isn't still uploading
elif fileage <= min_age_seconds:
print "File too new %s" % file
continue
return(filetime, filesize)
## Timeout
try:
filetime
filesize
raise UploadTimeoutTooSlow("File still uploading")
except NameError:
raise UploadTimeoutFileMissing("File not sent")
4 个回答
我想谈谈自定义异常,因为这对我来说非常重要。我会分享我的情况,读者可以和自己的情况对比一下。
我在一家视觉特效公司担任管道架构师,我的工作主要是开发我称之为“设施API”的系统。这是一个由许多模块组成的系统,处理从在文件系统中查找东西、管理模块/工具/项目配置,到处理来自各种计算机图形应用的数据类型,以便实现协作。
我非常努力地确保Python内置的异常不会被抛出。因为我们的开发者会依赖现有模块的生态系统来构建自己的工具,如果API让一个普通的IOError
冒出来,那就没什么意义了——尤其是调用的程序可能根本不知道它在读取文件系统(抽象化真是太美妙了)。如果底层模块无法对这个错误做出有意义的解释,那就需要做更多的工作。
我解决这个问题的方法是创建一个设施异常类,所有其他设施异常都从这个类派生。对于特定类型的任务或特定的主机应用程序,还有子类,这让我可以定制错误处理(例如,在Maya中抛出的异常会启动一个用户界面来帮助排查问题,因为通常的异常会在一个不起眼的控制台中抛出,常常会被忽视)。
设施异常类内置了各种报告功能——异常不会出现在用户面前,而是会在内部被报告。对于一系列异常,每当抛出一个异常时,我都会收到即时消息。其他异常则会安静地记录到一个数据库中,我可以查询最近的(每日或每周)报告。每个报告都链接到从用户会话中捕获的详细数据——通常包括截图、堆栈跟踪、系统配置等等。这意味着我可以在问题被报告之前有效地排查问题,并且手头的信息比大多数用户能提供的要多得多。
我不鼓励目的过于细分——异常接受传入的值(有时甚至是字典而不是字符串,如果我们想提供更多的排查数据)来生成格式化的输出。
所以,不,我并不认为每个模块定义一两个异常是不合理的——但它们需要有意义,并为项目增添价值。如果你只是把IOError
包裹成raise MyIOError("我遇到了IO错误!")
,那么你可能需要重新考虑一下。
每个方法定义一个或多个异常是不是不好的做法?
是的。
通常来说,每个模块定义一个异常更常见。当然,这也要看具体的情况。问题的关键在于:“你到底想捕捉什么?”
如果你在代码中根本不会用到 except ThisVeryDetailedException:
,那么这个非常详细的异常就没什么用处。
如果你可以这样写:except Error as e: if e.some_special_case
,只在少数几次情况下使用,那么你就可以轻松地将每个模块简化为一个异常,并把特殊情况作为异常的属性来处理,而不是用不同类型的异常。
常见的建议是(每个模块一个,命名为 Error
),这样你的代码通常会像这样。
try:
something
except some_module.Error as e:
carry on
这样你就有了一个不错的命名规则:module.Error
。这可以涵盖很多问题。
另外,如果你觉得自己有“潜在的竞争条件”,那么你可能需要重新设计一下,或者停止使用线程,转而使用多进程。如果你使用多进程,你会发现避免竞争条件非常简单。
每个方法定义一个或多个异常
如果你是说异常是在“方法内部”定义的,那是的,这样做是不好的习惯。如果你定义了两个异常,它们都是因为同一个错误而产生的,但因为是两个不同的方法抛出的,所以你就创建了两个异常,这样也是不好的。
如果你问在一个方法中抛出多个异常是否是坏习惯,那么不是,这样做是好的习惯。如果这些错误不属于同一类,定义多个异常是完全可以的。
一般来说,对于较大的模块,你会定义多个异常。如果你在做一个算术库,定义一个 ZeroDivisionError(除以零错误)和一个 OverflowError(溢出错误),这都是可以的,前提是这些异常在 Python 中没有被定义过,因为你当然可以重用它们。