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

Fah*_*tha 5 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"
Run Code Online (Sandbox Code Playgroud)

获得的错误是

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
Run Code Online (Sandbox Code Playgroud)

这个想法是为了应对某些代码或代码的清理(总是运行)或两者都给出错误的情况.例如,Ian Bicking在重新提出异常方面对此进行了一些讨论.在该帖子的最后,(参见参考资料Update:)他描述了如何处理代码+回滚/恢复的类似情况(仅在出错时运行).

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

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

使用locals()也有点难看.一个人能做得更好吗?

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

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

  1. 主代码或清理中的异常能够使程序停止运行.上下文管理器执行此操作,因为如果with循环的主体有异常而退出的主体没有异常,则传播该异常.如果exit抛出异常并且with循环的主体没有,则传播.如果两者都抛出异常,则 传播退出异常,并且抑制while循环体中的异常.这是所有记录的,即来自 Context Manager Types,

    contextmanager.exit(exc_type,exc_val,exc_tb)

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

  2. 如果两个地方都有异常,我希望看到两者都有回溯,即使技术上只抛出一个异常.这是基于实验的,因为如果两者都抛出异常,那么传播退出异常,但是仍然打印来自while循环体的回溯,如 srgerg的答案.但是,我无法在任何地方找到这个记录,这是不能令人满意的.

srg*_*erg 4

理想情况下,您可以使用 python with 语句来处理块内的清理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!"
Run Code Online (Sandbox Code Playgroud)

当我运行它时,它打印:

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!
Run Code Online (Sandbox Code Playgroud)

注意,任何一个异常都会终止程序,并且可以在语句中处理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!"
Run Code Online (Sandbox Code Playgroud)

这打印:

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!
Run Code Online (Sandbox Code Playgroud)