2012-01-02 42 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 
相關問題