簡體   English   中英

當主程序或清理中可能發生錯誤時的異常處理

[英]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 當然,這似乎比以前的方法有了很大的改進,例如,意粉代碼包含嘗試,例外和最終結果。

總而言之,我希望有兩種解決方案可以給我。

  1. 在主代碼或清理中出現異常的能力,以使程序停止在其軌道中。 上下文管理器這樣做是因為,如果with循環的主體具有異常,而退出主體沒有,則將傳播該異常。 如果exit引發異常,而with循環的主體沒有異常,則傳播該異常。 如果兩個都拋出異常,那么將傳播退出異常,並抑制while循環主體中的退出異常。 全部記錄在案,即來自Context Manager Types

    上下文管理員。 退出 (exc_type,exc_val,exc_tb)

    退出運行時上下文,並返回一個布爾值標志,指示是否應禁止發生的任何異常。 [...]從此方法返回真值將導致with語句抑制異常,並繼續在with語句之后立即使用該語句執行。 否則,異常將在此方法執行完后繼續傳播。 執行此方法期間發生的異常將替換with主體中發生的任何異常
    聲明。 [...]傳入的異常永遠不要顯式地引發。 而是,此方法應返回false值,以指示該方法已成功完成,並且不想抑制引發的異常。

  2. 如果兩個地方都存在異常,即使從技術上講只拋出一個異常,我也想查看兩者的回溯。 根據實驗,這是正確的,因為如果兩者都拋出異常,則將傳播退出異常,但是仍然打印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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM