在主程序或清理中可能发生错误时异常处理 [英] Exception handling when errors may occur in main program or in cleanup

查看:142
本文介绍了在主程序或清理中可能发生错误时异常处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  import sys 
try:
raise异常(main )
pass
除了:
exc_info = sys.exc_info()
finally:
try:
printcleanup - always run
提高异常(清除错误)
除了:
import traceback
print>> sys.stderr清理错误
traceback.print_exc()
如果locals()中的'exc_info':
raise exc_info [0],exc_info [1],exc_info [2]

打印正常退出

获取的错误是

 清理错误
追溯(最近的最后一次呼叫):
文件< stdin>,第10行,在< module>
异常:清理中的错误
清理 - 总是运行
追溯(最近的最后一次调用):
文件< stdin>,第3行,< module>
异常:错误在主

这个想法是应对一种情况,或者该代码的清理(总是运行)或两者都会产生错误。有一些讨论,例如,Ian Bicking在 Re提升异常。在这篇文章的结尾,(见 Update:),他描述了如何处理类似的代码+ rollback / revert(仅在出现错误的情况下运行)。 p>

我搞砸了这个,并提出了上面的代码,这是一个怪物。特别是,如果只有
cleanup中的错误(注释掉引发异常(主要错误)),则代码仍然正常退出,尽管它打印出回溯。目前,我提供了非清理错误的优先级,所以它会停止程序。



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



同样使用 locals()有点丑陋。可以做得更好吗?



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



总而言之,有两件事我想要一个解决方案给我。


  1. 在主代码或
    清理中出现异常的能力停止程序在其轨道。上下文管理器执行此操作,
    ,因为如果循环的正文具有异常,且
    的正文不会出现退出,则该异常将被传播。
    如果退出引发异常,并且循环的正文不存在,则
    然后传播。如果两个都抛出异常,那么退出
    异常被传播,而来自while循环体的那个被抑制为
    。这一切都记录在案,即从
    上下文管理器类型


    contextmanager。退出(exc_type,exc_val,exc_tb)



    退出运行时上下文并返回一个布尔标志,指示是否应该抑制发生的任何异常。 [...]
    从此方法返回真值将导致with语句阻止异常,并使用with with语句紧跟在
    语句后继续执行。否则,该方法完成
    执行后,异常继续传播。在执行此方法期间发生的异常将替换在

    语句正文中发生的任何异常。 [...]传递的例外不应该被明确地提及。相反,该方法应该返回一个false值到
    表示该方法成功完成,并且不想抑制引发的异常。



  2. 如果两个地方都有异常,我想从两者都看到traceback
    ,即使技术上只有一个异常被抛出。这是基于实验的
    true,因为如果两者都抛出异常,则
    然后退出异常被传播,但是从body的回调
    的while循环仍然打印,如
    srgerg的答案
    但是,我找不到这个记录的
    在任何地方,这是不能令人满意的。



解决方案

理想情况下,您可以使用python 与声明来处理 try ...除了块之外的清理,看起来像这样:

  class Something(object):
def __enter __(self):
print输入

def __exit __(self,t,
打印清理 - 总是运行
raise异常(__exit__期间发生异常)

try:
with Something()as something :
raise异常(异常发生!)
除了异常,e:
打印e
import traceback
traceback.print_exc(e)

打印正常退出!

当我运行它时,会打印:

 输入
清理 - 总是运行
在__exit__期间发生异常
追溯(最近的最后一次呼叫):
文件s3.py ,< module>中的第11行
raise异常(异常发生!)
文件s3.py,第7行,__exit__
raise异常(__exit__期间发生异常)
异常:异常发生在__exit__
正常退出!

注意,异常将终止程序,可以在

编辑:根据与上述相关的with语句文档, __ exit __()方法应该仅在 __ exit __()内存在错误时引发异常 - 即不应重新提出



如果中的代码与语句和 __ exit __()方法引发异常。在这种情况下,except子句中捕获的异常是在 __ exit __()中引发的异常。如果你想要在with语句中提出的那个,你可以这样做:

  class Something(object):
def __enter __(self):
print输入

def __exit __(self,t,v,tr):
printcleanup - always running
尝试:
raise异常(__exit__中发生异常)
除了异常,e:
if(t,v,tr)!=(无,无,无):
#__exit__调用一个现有的异常
返回False
其他:
#__exit__调用没有现有的异常
raise

try:
with Something()as something:
raise异常(异常发生!)
pass
除了异常,e:
print e
traceback.print_exc(e )
raise

打印正常退出!

打印:

 输入
清理 - 总是运行
异常发生!
追溯(最近的最后一次呼叫):
文件s2.py,第22行,< module>
raise异常(异常发生!)
异常:异常发生!
追溯(最近的最后一次呼叫):
文件s2.py,第22行,< module>
raise异常(异常发生!)
异常:异常发生!


This is with Python 2.6.6 (default) on Debian Squeeze. Consider the following Python code.

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"

The error obtained is

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

The idea is to cope with a situation where either some code or the cleanup of that code (which is always run) or both, gives an error. There is some discussion of this, for example, by Ian Bicking in Re-raising Exceptions. At the end of that post, (see Update:) he describes how to handle the similar case of code + rollback/revert (only run in case of error).

I fiddled with this and came up with the code above, which is a bit of a monstrosity. In particular, if there is only an error in cleanup (commenting out raise Exception("error in main")), the code still exits normally, though it does print out a traceback. Currently, I'm giving the non-cleanup error priority, so it gets to stop the program.

Ideally I'd like either error to stop the program, but that doesn't seem to easy to arrange. Python seems to only want to raise one error, losing the others if any, and by default it is usually the last one. Rearranging this gives rise to convolutions like above.

Also the use of locals() is a bit ugly. Can one do better?

EDIT: srgerg's answer introduced me to the notion of the context managers and the with keyword. In addition to PEP 343, the other relevant bits of documentation I found are (in no particular order). Context Manager Types, The with statement, and http://docs.python.org/reference/datamodel.html#context-managers. This certainly seems like a big improvement on previous approaches to this, i.e. spaghetti code involving trys, excepts, and finallys.

To summarize, there are two things that I want such a solution to give me.

  1. The ability for an exception in either the main code or in the cleanup to stop the program in its tracks. Context managers do this, because if the body of the with loop has an exception and the body of the exit does not, then that exception is propagated. If exit throws an exception and the body of the with loop does not, then that is propagated. if both throw an exception, then the exit exception is propagated and the one from the body of the while loop is suppressed. This is all documented i.e. from Context Manager Types,

    contextmanager.exit(exc_type, exc_val, exc_tb)

    Exit the runtime context and return a Boolean flag indicating if any exception that occurred should be suppressed. [...] Returning a true value from this method will cause the with statement to suppress the exception and continue execution with the statement immediately following the with statement. Otherwise the exception continues propagating after this method has finished executing. Exceptions that occur during execution of this method will replace any exception that occurred in the body of the with
    statement. [...] The exception passed in should never be reraised explicitly. instead, this method should return a false value to indicate that the method completed successfully and does not want to suppress the raised exception.

  2. If there are exceptions in both places, I want to see tracebacks from both, even if technically only one exception is thrown. This is true based on experimentation, because if both throw an exception, then the exit exception is propagated, but the traceback from the body of the while loop is still printed, as in srgerg's answer. However, I can't find this documented anywhere, which is unsatisfactory.

解决方案

Ideally, you'd use the python with statement to handle the cleanup within the try ... except block, which would look something like this:

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!"

When I run this, it prints:

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!

Note, either exception will terminate the program, and can be dealt with in the except statement.

Edit: According to the with statement documentation linked to above, the __exit__() method should only raise an exception if there is an error inside __exit__() - that is, it should not re-raise the exception passed into it.

This is a problem if both the code in the with statement and the __exit__() method raise an exception. In that case, the exception that is caught in the except clause is the one raised in __exit__(). If you want the one raised in the with statement, you can do something like this:

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!"

This prints:

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!

这篇关于在主程序或清理中可能发生错误时异常处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆