主程序或清理中可能发生错误时的异常处理

时间:2012-01-02 08:23:40

标签: python exception-handling

这是Debian Squeeze上的Python 2.6.6(默认)。请考虑以下Python代码。

import sys
try:
    raise Exception("error in main")
    pass
except:
    exc_info = sys.exc_info()
finally:
    try:
        print "cleanup - always run"
        raise Exception("error in cleanup")
    except:
        import traceback
        print >> sys.stderr, "Error in cleanup"
        traceback.print_exc()
    if 'exc_info' in locals():
        raise exc_info[0], exc_info[1], exc_info[2]

print "exited normally"

获得的错误是

Error in cleanup
Traceback (most recent call last):
  File "<stdin>", line 10, in <module>
Exception: error in cleanup
cleanup - always run
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
Exception: error in main

这个想法是为了应对某些代码或代码的清理(总是运行)或两者都给出错误的情况。例如,Ian Bicking在Re-raising Exceptions中对此进行了一些讨论。在该帖子的最后,(参见Update:)他描述了如何处理代码+回滚/恢复的类似情况(仅在出现错误时运行)。

我摆弄了这个并提出了上面的代码,这有点像怪物。特别是,如果中有错误 清理(​​注释掉raise Exception("error in main")),代码仍然正常退出,但它确实打印出一个追溯。目前,我给出了非清除错误优先级,因此它可以停止程序。

理想情况下,我想要阻止程序出错,但这似乎并不容易安排。 Python似乎只想提出一个错误,如果有的话会丢失其他错误,默认情况下它通常是最后一个错误。重新排列会产生如上所述的卷积。

使用locals()也有点难看。一个人可以做得更好吗?

编辑:srgerg's answer向我介绍了上下文管理器和with关键字的概念。除了PEP 343之外,我发现的其他相关文档(没有特别的顺序)。 Context Manager TypesThe with statementhttp://docs.python.org/reference/datamodel.html#context-managers。对于以前的方法来说,这似乎是一个很大的改进,即涉及trys,excepts和finallys的意大利面条代码。

总而言之,有两件事我想要这样的解决方案给我。

  1. 主要代码或中的异常能力 清理程序以阻止程序。上下文管理员这样做, 因为如果with循环的主体有一个例外和身体 退出没有,然后传播该异常。 如果退出会抛出异常并且with循环的主体没有, 然后传播。如果两者都抛出异常,那么退出 传播异常,而while循环体中的异常是 抑制。这些都记录在案,即来自 Context Manager Types

      

    contextmanager。退出(exc_type,exc_val,exc_tb)

         

    退出运行时上下文并返回一个布尔标志,指示是否应该抑制发生的任何异常。 [...]   从此方法返回true值将导致with语句抑制异常并继续执行   声明后紧跟声明。否则,在此方法完成后,异常将继续传播   执行。执行此方法期间发生的异常将替换在with的正文中发生的任何异常   声明。 [...]传递的异常不应该明确地重新加注。相反,此方法应返回false值   表示该方法已成功完成,并且不希望抑制引发的异常。

  2. 如果这两个地方都有例外,我想看看追溯 来自两者,即使技术上只抛出一个例外。这是 true基于实验,因为如果两者都抛出异常, 然后传播退出异常,但是来自正文的回溯 仍然打印while循环,如 srgerg's answer。 但是,我无法找到这个记录 任何地方,都不能令人满意。

3 个答案:

答案 0 :(得分:4)

理想情况下,你可以使用python with statement来处理try ... except块内的清理,它看起来像这样:

class Something(object):
    def __enter__(self):
        print "Entering"

    def __exit__(self, t, v, tr):
        print "cleanup - always runs"
        raise Exception("Exception occurred during __exit__")

try:
    with Something() as something:
        raise Exception("Exception occurred!")
except Exception, e:
    print e
    import traceback
    traceback.print_exc(e)

print "Exited normally!"

当我运行它时,会打印:

Entering
cleanup - always runs
Exception occurred during __exit__
Traceback (most recent call last):
  File "s3.py", line 11, in <module>
    raise Exception("Exception occurred!")
  File "s3.py", line 7, in __exit__
    raise Exception("Exception occurred during __exit__")
Exception: Exception occurred during __exit__
Exited normally!

注意,任何一个异常都将终止该程序,并且可以在except语句中处理。

修改:根据上面链接的with语句文档,__exit__()方法只应在__exit__()内发生错误时引发异常 - 也就是说,不应该重新提出传递给它的异常。

如果with语句和__exit__()方法中的代码都引发异常,则会出现此问题。在这种情况下,except子句中捕获的异常是__exit__()中引发的异常。如果你想要在with语句中引发的那个,你可以这样做:

class Something(object):
    def __enter__(self):
        print "Entering"

    def __exit__(self, t, v, tr):
        print "cleanup - always runs"
        try:
            raise Exception("Exception occurred during __exit__")
        except Exception, e:
            if (t, v, tr) != (None, None, None):
                # __exit__ called with an existing exception
                return False
            else:
                # __exit__ called with NO existing exception
                raise

try:
    with Something() as something:
        raise Exception("Exception occurred!")
        pass
except Exception, e:
    print e
    traceback.print_exc(e)
    raise

print "Exited normally!"

打印:

Entering
cleanup - always runs
Exception occurred!
Traceback (most recent call last):
  File "s2.py", line 22, in <module>
    raise Exception("Exception occurred!")
Exception: Exception occurred!
Traceback (most recent call last):
  File "s2.py", line 22, in <module>
   raise Exception("Exception occurred!")
Exception: Exception occurred!

答案 1 :(得分:1)

通过提供自定义exception hook

,可以获得类似的行为
import sys, traceback

def excepthook(*exc_info):
    print "cleanup - always run"
    raise Exception("error in cleanup")
    traceback.print_exception(*exc_info)
sys.excepthook = excepthook

raise Exception("error in main")

示例输出:

cleanup - always run
Error in sys.excepthook:
Traceback (most recent call last):
  File "test.py", line 5, in excepthook
    raise Exception("error in cleanup")
Exception: error in cleanup

Original exception was:
Traceback (most recent call last):
  File "test.py", line 9, in <module>
    raise Exception("error in main")
Exception: error in main

在此示例中,代码的工作方式如下:

  • 如果未捕获异常,则执行excepthook
  • 在打印例外之前,excepthook运行一些清理代码(原始问题中的finally下)。
  • 如果钩子中引发异常,则会打印该异常,之后还会打印原始异常。

注意:我没有找到任何关于在钩子中出现问题时打印原始异常的文档,但我在cpython和jython中都看到过这种行为。特别是在cpython中我看到了以下实现:

void
PyErr_PrintEx(int set_sys_last_vars)
{
    ...
    hook = PySys_GetObject("excepthook");
    if (hook) {
        ...
        if (result == NULL) {
            ...
            PySys_WriteStderr("Error in sys.excepthook:\n");
            PyErr_Display(exception2, v2, tb2);
            PySys_WriteStderr("\nOriginal exception was:\n");
            PyErr_Display(exception, v, tb);
            ...
        }
    }
}

答案 2 :(得分:0)

你非常接近一个简单的解决方案。只需在第一个异常中使用traceback.print_exc() - 然后您就不必再处理第二个异常了。这可能是这样的:

error7 = False
try:
    raise Exception("error in main")
    pass
except:
    import traceback
    traceback.print_exc()
    error7 = True
finally:
    print "cleanup - always run"
    raise Exception("error in cleanup")
    if error7:
        raise SystemExit()

print "exited normally"

是否抛出异常的信息存储在error7中,如果是这种情况,则在SystemExit()块的末尾引发finally

启用了raise个语句的输出:

cleanup - always run
Traceback (most recent call last):
  File "G:/backed-up to mozy/Scripts/sandbox.py", line 3, in <module>
    raise Exception("error in main")
Exception: error in main
Traceback (most recent call last):

  File "<ipython-input-1-10089b43dd14>", line 1, in <module>
    runfile('G:/backed-up to mozy/Scripts/sandbox.py', wdir='G:/backed-up to mozy/Scripts')

  File "C:\Anaconda2\lib\site-packages\spyder\utils\site\sitecustomize.py", line 866, in runfile
    execfile(filename, namespace)

  File "C:\Anaconda2\lib\site-packages\spyder\utils\site\sitecustomize.py", line 87, in execfile
    exec(compile(scripttext, filename, 'exec'), glob, loc)

  File "G:/backed-up to mozy/Scripts/sandbox.py", line 11, in <module>
    raise Exception("error in cleanup")

Exception: error in cleanup