[英]Exception handling when errors may occur in main program or in cleanup
這是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在“ 重新引發異常”中對此進行了一些討論。 在那篇文章的結尾(請參閱Update:
:),他描述了如何處理類似的代碼+回滾/還原(僅在出現錯誤的情況下運行)。
我擺弄了這個,並想出了上面的代碼,這有點怪異。 特別是,如果清理中僅存在錯誤(注釋出raise Exception("error in main")
),則該代碼仍會正常退出,盡管它確實會打印出一個回溯。 當前,我給非清除錯誤優先級,因此它可以停止程序。
理想情況下,我希望通過任何一個錯誤來停止程序,但這似乎並不容易。 Python似乎只想引發一個錯誤,如果有的話就丟失其他錯誤,默認情況下通常是最后一個錯誤。 重新排列會引起如上的卷積。
同樣,使用locals()
有點難看。 能做得更好嗎?
編輯: srgerg的答案向我介紹了上下文管理器和with
關鍵字的概念。 除了PEP 343以外 ,我發現的其他相關文檔(沒有特定順序)也是如此。 上下文管理器類型 , with語句和http://docs.python.org/reference/datamodel.html#context-managers 。 當然,這似乎比以前的方法有了很大的改進,例如,意粉代碼包含嘗試,例外和最終結果。
總而言之,我希望有兩種解決方案可以給我。
在主代碼或清理中出現異常的能力,以使程序停止在其軌道中。 上下文管理器這樣做是因為,如果with循環的主體具有異常,而退出主體沒有,則將傳播該異常。 如果exit引發異常,而with循環的主體沒有異常,則傳播該異常。 如果兩個都拋出異常,那么將傳播退出異常,並抑制while循環主體中的退出異常。 全部記錄在案,即來自Context Manager Types ,
上下文管理員。 退出 (exc_type,exc_val,exc_tb)
退出運行時上下文,並返回一個布爾值標志,指示是否應禁止發生的任何異常。 [...]從此方法返回真值將導致with語句抑制異常,並繼續在with語句之后立即使用該語句執行。 否則,異常將在此方法執行完后繼續傳播。 執行此方法期間發生的異常將替換with主體中發生的任何異常
聲明。 [...]傳入的異常永遠不要顯式地引發。 而是,此方法應返回false值,以指示該方法已成功完成,並且不想抑制引發的異常。
如果兩個地方都存在異常,即使從技術上講只拋出一個異常,我也想查看兩者的回溯。 根據實驗,這是正確的,因為如果兩者都拋出異常,則將傳播退出異常,但是仍然打印while循環主體中的回溯,如srgerg的answer所示 。 但是,我在任何地方都找不到此文檔,這不能令人滿意。
理想情況下,您可以使用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!"
當我運行它時,它打印:
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__()
引發的__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!
通過提供定制的異常掛鈎,可以獲得類似的行為:
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);
...
}
}
}
您已經接近一個簡單的解決方案。 只需在第一個異常中使用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
,如果是,則在finally
塊的末尾引發SystemExit()
。
啟用兩個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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.