2012-01-02 25 views
5

这是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 Types,The with statementhttp://docs.python.org/reference/datamodel.html#context-managers。这对于以前的方法来说似乎是一个很大的改进,即涉及trys,excepts和finallys的意大利面代码。

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

  1. 在其轨道到停止程序的能力为在任一主代码或在 清理异常。上下文管理器执行此操作,因为如果with循环的主体出现异常并且主体 出口没有,则会传播该异常。 如果退出抛出一个异常,并且with循环的主体没有, 然后传播。如果两者都抛出异常,则出口 异常被传播,并且来自while循环体的一个被抑制,即 。这全部记录在案,即从 Context Manager Types,

    contextmanager。 exit(exc_type,exc_val,exc_tb)

    退出运行环境并返回一个布尔标志,指示是否应该抑制发生的任何异常。 [...] 从此方法返回真值将导致with语句抑制异常,并在with语句后立即继续执行 语句。否则,此方法执行完毕 后异常继续传播。在执行此方法期间发生的异常将替换发生在
    语句正文中的任何异常。 [...]通过的例外不应该明确重新考虑。相反,此方法应返回一个假值为 ,表示该方法已成功完成,并且不希望抑制引发的异常。

  2. 如果在这两个地方例外,我希望看到回溯从两个 ,即使在技术上只有一个异常被抛出。这是真正的 基于实验的,因为如果两个抛出一个异常, 那么出口异常传播,但是从while循环体 回溯仍印刷,如 srgerg's answer。 但是,我无法在任何地方找到此记录的 ,这是不令人满意的。

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

    输出示例::

回答

4

理想情况下,你会使用Python with statementtry ... 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! 

注意,无论是异常将终止程序,并可以在被处理3210声明。

编辑:根据链接到上面的with语句文档中,__exit__()方法只如果有内部__exit__()错误引发异常 - 也就是说,它不应该再提高传递给它的异常。

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

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! 
+0

谢谢,对我来说这是一个全新的想法。是否有清除你提出的异常(“在__exit __期间发生异常”)的想法?如果是这样,你可以添加一个合适的'打印'清理 - 总是运行“'或类似的? – 2012-01-02 09:28:01

+0

我已经按要求添加了打印语句。 – srgerg 2012-01-02 09:43:32

+0

谢谢,srgerg。我正在阅读PEP。我认为我以前没有听说过或看过“with”关键字,但也许我只是没有注意。将一个清理代码放入输入或退出函数中,还是不会产生影响? – 2012-01-02 09:45:48

1

类似的行为可以通过提供定制的exception hook获得

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 

在这个例子中的代码的工作原理如下:

  • 如果异常不是' t被捕获,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); 
      ... 
     } 
    } 
} 
+0

谢谢,但我不清楚这是如何在我的脚本的环境中工作。你能用一个类似的例子来说明,或者可能是同一个例子吗? – 2012-01-02 09:17:15

+0

@FaheemMitha我编辑了我的答案以添加更多信息。如果我理解正确,你的问题和我的答案中的例子实际上是一样的。 – jcollado 2012-01-02 09:33:46

+0

谢谢,你的解释有助于澄清事情。似乎excepthook运行在范围内的所有异常,对吗?我如何将它本地化为一个特定的环境?我不希望它在任何情况下运行。 – 2012-01-02 09:42:59

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 
相关问题