关于错误处理:在Python中捕获嵌套异常

Capture Nested Exceptions in Python

我有一组Python脚本,它们以嵌套的方式调用函数。对于这些函数中的每一个,我都有一个try,除了语句来捕获每个异常并打印它们。我想发送一封电子邮件警报,其中包含执行期间遇到的完整异常序列。例:

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
import sys

def SendAlert(ErrorMessage):
    try:
        #send email alert with error message
        #[...]
    except:
        print(str(sys.exc_info()))
        return(sys.exc_info())

def ParentFunction():
    try:
        #call ChildFunction
        ChildResult = ChildFunction()

        #do stuff with ChildResult
        #[...]
        return ParentResult
    except:
        ErrorMessage = str(sys.exc_info())
        print(ErrorMessage)
        SendAlert(ErrorMessage)

def ChildFunction():
    try:
        #do stuff
        #[...]
        return ChildResult
    except:
        print(str(sys.exc_info()))
        return(sys.exc_info())

#main
if __name__ == '__main__':
    Result = ParentFunction()

如果ChildFunction中出现错误,上面的代码将表现如下,这是最嵌套的函数:

  • ChildFunction遇到异常,它将打印并返回
    错误消息到ParentFunction
  • ParentFunction将失败,因为ChildResult包含错误消息而不是有效值
  • ParentFunction将触发和异常,并在电子邮件警报中发送自己的错误消息

除了来自ParentFunction的错误消息之外,我希望电子邮件警报包含来自ChildFunction的错误消息。请注意,我想避免在ParentFunction的except语句中将ChildResult变量传递给SendAlert函数,因为在现实生活中我的程序有很多嵌套函数,这意味着将每个函数的结果变量传递给除了声明。

你会如何实现这一目标?有没有办法访问整个程序触发的完整错误序列?

谢谢


你不需要返回用sys.exc_info获得的异常:我们可以重新提升它

1
2
3
4
5
6
try:
    # do stuff
# FIXME: we should avoid catching too broad exception
except Exception as err:
    # do stuff with exception
    raise err

所以你的例子看起来像

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
def SendAlert(ErrorMessage):
    try:
        # send email alert with error message
        # [...]
        pass
    # what kind of exceptions may occur while sending email?
    except Exception as err:
        print(err)
        raise err


def ParentFunction():
    try:
        # call ChildFunction
        ChildResult = ChildFunction()

        ParentResult = ChildResult
        # do stuff with ChildResult
        # [...]
        return ParentResult
    # FIXME: we should avoid catching too broad exception
    except Exception as err:
        ErrorMessage = str(err)
        # why do we need to print again?
        print(ErrorMessage)
        SendAlert(ErrorMessage)


def ChildFunction():
    try:
        ChildResult = 0
        # do stuff
        # [...]

        # let's raise `ZeroDivisionError`

        ChildResult /= 0

        return ChildResult
    # FIXME: we should avoid catching too broad exception
    except Exception as err:
        print(err)
        raise err


# main
if __name__ == '__main__':
    Result = ParentFunction()

进一步改进

对于打印完整错误回溯,我们可以使用logging模块

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
50
51
import logging

logging.basicConfig(level=logging.DEBUG)

logger = logging.getLogger(__name__)


def SendAlert(ErrorMessage):
    try:
        # send email alert with error message
        # [...]
        pass
    # what kind of exceptions may occur while sending email?
    except Exception as err:
        logger.exception('Error while sending email')
        # we're not receiving values from this function
        raise err


def ParentFunction():
    try:
        # call ChildFunction
        ChildResult = ChildFunction()

        ParentResult = ChildResult
        # do stuff with ChildResult
        # [...]
        return ParentResult
    # FIXME: we should avoid catching too broad exception
    except Exception as err:
        # this will log full error traceback
        # including `ChildFunction`
        logger.exception('Error in ParentFunction')
        ErrorMessage = str(err)
        SendAlert(ErrorMessage)


def ChildFunction():
    ChildResult = 0
    # do stuff
    # [...]

    # e. g. let's raise `ZeroDivisionError`
    ChildResult /= 0

    return ChildResult


# main
if __name__ == '__main__':
    Result = ParentFunction()

它只是冰山一角,logging很棒,你绝对应该使用它。

进一步阅读

  • 记录HOWTO,
  • SMTPHandler,用于通过SMTP发送电子邮件错误


您还可以创建一个custom exception,它可以获取描述性错误消息并将其返回。

这是一个简单的例子,您可以修改它并将其实现到您的代码中,直到它满足您的需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyCustomError(Exception):
    def __init__(self, err):
        Exception.__init__(self)
        self.error = err
    def __str__(self):
        return"%r" % self.error

a = 1
try:
    if a != 0:
        raise MyCustomError("This is an Error!")
except MyCustomError as err:
    print(err)

输出:

1
'This is an Error!'