在Python中捕获嵌套异常

5
我有一组 Python 脚本,这些脚本以嵌套方式调用函数。对于每个函数,我都有一个 try-except 语句来捕获所有异常并将它们打印出来。我希望能够发送一封包含执行期间遇到的所有异常的完整序列的电子邮件提醒。举个例子:
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 变量,因为在实际情况下,我的程序有很多嵌套函数,这意味着需要将每个单个函数的结果变量传递到 except 语句中。

你怎么实现这个功能?是否有一种方法可以访问整个程序触发的完整错误序列?

谢谢


1
重新引发异常怎么样? - Azat Ibrakov
你是什么意思? - Alexis.Rolland
1
为什么不使用 try ... except Exception as error - Chiheb Nexus
1
谢谢Chiheb,这也是Azat在下面推荐的。我会尝试的。 - Alexis.Rolland
2个回答

8

您不需要返回使用sys.exc_info获取的异常:我们可以直接重新抛出它

try:
    # do stuff
# FIXME: we should avoid catching too broad exception
except Exception as err:
    # do stuff with exception
    raise err

所以你的例子可能看起来像这样:
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 模块,例如:

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非常棒,你绝对应该使用它。

进一步阅读


感谢您的进一步改进,我刚刚测试了上面的脚本。有一个问题,由于脚本在ParentFunction中重新引发和记录异常,并带有完整的异常序列,也许我不需要在ChildFunction中再次记录它?否则,日志将变得非常繁琐,有很多冗余信息,因此可读性会降低。您认为呢? - Alexis.Rolland
@Alexis.Rolland:当然,你不应该记录相同的异常两次,所以请随意删除ChildFunction内部的日志记录。 - Azat Ibrakov
好的,抱歉如果这个问题听起来很愚蠢,但是如果我从ChildFunction中字面上删除try...except子句会怎么样呢?ParentFunction仍然能够记录日志吗?(现在无法测试,因为我在手机上) - Alexis.Rolland
我是指当然要记录完整的异常序列... :) - Alexis.Rolland

1
你还可以创建自定义异常,它可以带有描述性错误消息并返回它。这是一个简单的例子,你可以修改并将其实现到你的代码中,以满足你的需求:
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)

输出:

'This is an Error!'

谢谢Chiheb,我对类还比较陌生,你能详细解释一下它在这里的作用和好处吗? - Alexis.Rolland
1
通过这种自定义异常(类),您可以轻松追踪代码中的错误,同时还可以将其他重复的代码添加到您的类中。例如,当出现异常时,您想将错误记录到文件中,然后继续处理其余的代码。或者,当出现异常时,您想在执行其余代码之前调用一个方法/函数。没有限制...这也是一种将异常分类成组的便捷技巧。等等... - Chiheb Nexus
更多信息请参见此处 - Chiheb Nexus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接