多个调用级别的异常处理

时间:2019-02-26 21:01:33

标签: python exception-handling try-except

如何最好地处理调用层次结构中引发异常的方法的多个级别,以便如果是致命错误,程序将退出(在显示错误对话框之后)?

我基本上来自Java。在那里,我只需将任何方法声明为throws Exception,然后将其重新抛出并在顶层将其捕获即可。

但是,Python是不同的。我的Python代码基本上如下所示。

编辑:添加了更简单的代码...

主要输入功能(plugin.py):

def main(catalog):

    print "Executing main(catalog)... "
    # instantiate generator
    gen = JpaAnnotatedClassGenerator(options)

    # run generator
    try:
        gen.generate_bar()  # doesn't bubble up
    except ValueError as error:
        Utilities.show_error("Error", error.message, "OK", "", "")
        return

    ... usually do the real work here if no error

JpaAnnotatedClassGenerator类(engine.py):

class JpaAnnotatedClassGenerator:

    def generate_bar(self):
        self.generate_value_error()

    def generate_value_error(self):
        raise ValueError("generate_value_error() raised an error!")

我想返回一个带有异常的调用者,该异常将被抛出该调用,直到到达最外面的try-except为止,以显示带有异常消息的错误对话框。

问题: 如何在Python中做到最好?我真的必须为每个被调用的方法重复try-except吗?

顺便说一句:我正在使用Python 2.6.x,由于绑定到提供解释器的MySQL Workbench而无法升级(Python 3在其升级列表中)。

2 个答案:

答案 0 :(得分:2)

如果您没有捕获到异常,它将使调用堆栈冒泡,直到有人发现。如果没有人抓住它,则运行时将获得它并死于异常错误消息和完整的追溯。 IOW,您不必在任何地方显式地捕获和重新引发您的异常-这实际上将使拥有异常的整个观点无法实现。实际上,尽管异常主要用于错误/意外情况,但异常首先还是控制流工具,它允许脱离常规的执行流,并将控制(和某些信息)传递给调用堆栈中的任意位置。

从这个POV来看,您的代码似乎是最正确的(注意:我没有花时间看整个内容,只是看了一眼),除了(没有双关语)有几点:

首先,您应该定义自己的特定异常类,而不是使用内置的ValueError(如果对您有意义,则可以从中继承),这样您可以确保仅捕获所需的确切异常(相当大)您自己的代码“下”几层可能会引发您没有想到的ValueError。

然后,您可能(也可能不,取决于代码的使用方式)还想在main()函数中添加一个包含所有内容的顶级处理程序,以便您可以正确登录(使用{{1 }}模块)所有错误,最终释放资源,在进程终止之前进行一些清理等工作。

作为旁注,您可能还想学习和使用正确的字符串格式,并且-如果perfs至少是一个问题-避免重复这样的常量调用:

logger

鉴于Python的动态特性,编译器和运行时都无法安全地优化那些重复的调用(该方法可以在调用之间动态地重新定义),因此您必须自己进行操作。

编辑:

  

当尝试捕获main()函数中的错误时,不会冒异常,但是当我使用此模式更深一层时,冒泡似乎可以工作。

您可以使用简单的MCVE轻松检查其是否正常工作:

elif AnnotationUtil.is_embeddable_table(table) and AnnotationUtil.is_secondary_table(table):
    # ...
elif AnnotationUtil.is_embeddable_table(table):
    # ...
elif AnnotationUtil.is_secondary_table(table):
    # ...
  

看来,提供Python env的软件以某种错误的方式对待了主插件文件。看起来我将不得不检查MySQL Workbench成员

Uhu ...即使是嵌入式的,机制期望也应该仍然可以按预期的方式工作-至少对于取决于您的def deeply_nested(): raise ValueError("foo") def nested(): return deeply_nested() def firstline(): return nested() def main(): try: firstline() except ValueError as e: print("got {}".format(e)) else: print("you will not see me") if __name__ == "__main__": main() 函数的调用栈部分(无法分辨出调用栈上部发生了什么) )。但是考虑到MySQL如何处理错误(如何将数据静默地截断?),如果它们侵入运行时以静默方式传递插件代码xD中的任何错误,我将不会感到特别惊讶。

答案 1 :(得分:1)

可以将错误冒出来

Python的异常未经检查,这意味着您没有义务声明或处理它们。即使您知道可能会出现某些问题,也只有在打算采取措施时才捕获错误。拥有异常透明层是很好的,这些层会在异常冒泡时通过它们正常退出:

def logged_get(map: dict, key: str):
    result = map[key]  # this may raise, but there is no state to corrupt
    # the following is not meaningful if an exception occurred
    # it is fine for it to be skipped by the exception bubbling up
    print(map, '[%s]' % key, '=>', result)
    return result

在这种情况下,logged_get将只转发由查找引发的任何KeyError(及其他)。 如果外部呼叫者知道如何处理错误,则可以这样做。

因此,只需按您的方式致电self.create_collection_embeddable_class_stub

错误可以杀死应用程序

即使没有任何错误可以处理,解释器也可以处理。您会得到一个堆栈跟踪,显示出哪里出错了以及在哪里。这种致命错误“仅在存在错误的情况下会发生”,可以“安全地”冒泡以显示出问题所在。

实际上,退出解释器和断言也使用此机制。

>>> assert 2 < 1, "This should never happen"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AssertionError: This should never happen

对于许多服务,即使在部署中也可以使用它-例如,systemd会将其记录为Linux系统服务。仅在出于安全考虑或用户无法处理错误的情况下,才尝试抑制外部错误。

可以使用精确的错误

由于未选中异常,因此可以使用任意许多而不过度紧张API。这样可以使用自定义错误来表示不同级别的问题:

class DBProblem(Exception):
    """Something is wrong about our DB..."""

class DBEntryInconsistent(DBProblem):
    """A single entry is broken"""

class DBInconsistent(DBProblem):
    """The entire DB is foobar!"""

除非您的用例确实符合它们的含义,否则通常不要重复使用内置错误。这样可以根据需要精确地处理错误:

try:
    gen.generate_classes(catalog)
except DBEntryInconsistent:
    logger.error("aborting due to corrupted entry")
    sys.exit(1)
except DBInconsistent as err:
    logger.error("aborting due to corrupted DB")
    Utility.inform_db_support(err)
    sys.exit(1)
# do not handle ValueError, KeyError, MemoryError, ...
# they will show up as a stack trace