在python中定义异常的频率应该多少?

How often should custom exceptions be defined in python?

在试图消除Python模块中潜在的竞争条件时,我写了一些专门的工作流,我了解了Python的"请求宽恕比许可更容易"(EAFP)编码风格,现在我提出了许多自定义异常,其中包括我以前使用if/thens的try/except块。

我对Python不熟悉,这种EAFP风格在逻辑上讲得通,似乎使我的代码更加健壮,但这方面的一些东西让人觉得太过火了。每个方法定义一个或多个异常是一种坏做法吗?

这些自定义异常通常只对单个方法有用,尽管它看起来像是一个功能正确的解决方案,但似乎需要维护很多代码。

这里有一个示例方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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")


define one or more exceptions per method

如果您的意思是,异常实际上是按照"方法体内部"中的方法定义的,那么是的。这是不好的做法。如果您定义了两个与同一错误相关的异常,但由于两个不同的方法引发了两个异常,则也是如此。

如果您询问每个方法引发多个异常是否是坏的实践,那么不,这是好的实践。如果错误不属于同一类别,那么为每个模块定义几个异常是完全可以的。

通常,对于较大的模块,您将定义多个异常。如果您要处理一些算术库,并且要定义一个零分区错误和一个溢出错误(如果它们还没有在python中定义,因为您当然可以重复使用它们),那将是非常好的。


我要考虑一下这个问题,因为习惯例外对我来说是很重要的。我来解释一下我的情况,读者可以根据自己的情况来权衡。

我是一家视觉特效公司的管道架构师——我所做的大部分工作都涉及到开发我所称的"Facility API"——这是一个由许多模块组成的系统,可以处理从文件系统上定位事物、管理模块/工具/项目配置到处理各种CG应用程序的数据类型以实现协作的所有事务。

我会尽最大努力确保Python的内置异常不会冒泡。由于我们的开发人员将依赖现有模块的生态系统来构建他们自己的工具,因此让API让一个通用的IOError转义会适得其反——特别是因为调用例程可能甚至不知道它正在读取文件系统(抽象是一件很好的事情)。如果底层模块无法表达对该错误有意义的内容,则需要做更多的工作。

我解决这个问题的方法是创建一个Facility异常类,从中派生所有其他Facility异常。对于特定类型的任务或特定的主机应用程序,有这样的子类-这允许我自定义错误处理(例如,在maya中引发的异常将启动一个UI以帮助进行故障排除,因为通常的异常将在不显眼的控制台中引发,并且经常会被忽略)。

所有类型的报告都内置在Facility Exception类中——异常在用户看来不会同时在内部报告。对于一系列的例外情况,我会在任何时候提出一个即时消息。其他人只是悄悄地向数据库报告,我可以查询最近(每日或每周)的报告。每个链接都指向从用户会话中捕获的大量数据——通常包括屏幕截图、堆栈跟踪、系统配置以及更多。这意味着我可以在报告问题之前有效地对其进行故障排除——而且我掌握的信息比大多数用户可能提供的要多。

不鼓励使用非常精细的分级-例外情况接受传递的值(有时甚至是字典而不是字符串,如果我们想提供大量数据进行故障排除),以提供格式化的输出。

所以不-我不认为为每个模块定义一个或两个异常是不合理的-但是它们需要有意义并为项目添加一些东西。如果你只是把一个IOError包装到raise MyIOError("I got an IO error!"),那么你可能需要重新考虑一下。


Is is bad practice to define one or more exceptions per method?

对。

每个模块一个更为典型。当然,这取决于详细的语义。问题归结为:"你真正想抓住什么?"

如果在代码中永远不使用except ThisVeryDetailedException:,那么非常详细的异常就没有什么帮助。

如果您可以这样做:except Error as e: if e.some_special_case对于它非常重要的几次,那么您可以很容易地将每个模块简化为一个异常,并将特殊情况作为异常的属性处理,而不是不同类型的异常。

常见的建议(每个模块一个,名为Error)意味着您的代码通常会像这样。

1
2
3
4
try:
    something
except some_module.Error as e:
    carry on

这给了您一个很好的命名约定:module.Error。这涵盖了许多罪恶。

在一个不相关的注释中,如果您认为您有"潜在的竞争条件",那么您应该正确地重新设计事物,或者停止尝试使用线程或切换到多处理。如果您使用多处理,您会发现很容易避免竞争条件。


我认为没有必要对每种可能的情况都有一个非常具体的例外。一个单独的UploadTimeoutError可能会很好,您可以自定义异常字符串——毕竟字符串就是为这个字符串而设计的。注意,对于每种可能的语法错误类型,python都没有单独的异常,只是一个普通的SyntaxError

此外,是否有必要为每个自定义异常定义__init____str__方法?据我所知,如果您没有实现任何异常行为,则不需要添加任何代码:

1
2
3
4
5
6
7
8
>>> class MyException(Exception): pass
...
>>> raise MyException("oops!")
Traceback (most recent call last):
  File"<ipython console>", line 1, in <module>
MyException: oops!    
>>> str(MyException("oops!"))
'oops!'