2013-07-16 237 views
9

添加上下文例外,我想上下文到一個例外是這樣的:我怎樣才能在Python

def process(vals): 
    for key in vals: 
     try: 
      do_something(vals[key]) 
     except Exception as ex: # base class. Not sure what to expect. 
      raise # with context regarding the key that was being processed. 

我發現是一反常態長篇大論的Python的方式。有沒有比這更好的方法?

try: 
    do_something(vals[key]) 
except Exception as ex: 
    args = list(ex.args) 
    if len(args) > 1: 
     args[0] = "{}: {}".format(key, args[0]) 
     ex.args = tuple(args) 
    raise # Will re-trhow ValueError with new args[0] 
+0

裏面除了塊'ex.args =(鍵)+ ex.args'有點清潔? –

+0

@SteveAllison:你可以這樣做,但是這個消息會以一個元組的形式出現,比如'ZeroDivisionError:('0:','除零')'。 – unutbu

回答

2

你可以只提出一個新的異常:

def process(vals): 
    for key in vals: 
     try: 
      do_something(vals[key]) 
     except Exception as ex: 
      raise Error(key, context=ex) 

在Python 3裏,你不需要明確提供舊的例外,這將作爲__context__屬性的新的異常對象上,默認異常處理程序會自動舉報:

def process(vals): 
    for key in vals: 
     try: 
      do_something(vals[key]) 
     except Exception: 
      raise Error(key) 

在你情況下,您應該使用明確的raise Error(key) from ex語法來設置新異常的__cause__屬性,請參閱Exception Chaining and Embedded Tracebacks


如果唯一的問題是在您的問題中消息修正代碼的詳細程度,你可以將其封裝在一個函數:

try: 
    do_something(vals[key]) 
except Exception: 
    reraise_with_context(key=key) # reraise with extra info 

其中:

import inspect 
import sys 

def reraise_with_context(**context): 
    ex = sys.exc_info()[1] 
    if not context: # use locals from the caller scope 
     context = inspect.currentframe().f_back.f_locals 
    extra_info = ", ".join("%s=%s" % item for item in context.items()) 
    amend_message(ex, extra_info) 
    raise 

def amend_message(ex, extra): 
    msg = '{} with context: {}'.format(ex.args[0], extra) if ex.args else extra 
    ex.args = (msg,) + ex.args[1:] 
+0

感謝J.F.我沒有考慮鏈接異常來解決我的問題。該解決方案的問題在於,默認的python異常處理程序將堆棧跟蹤顯示在彼此之上,因此必須進行一些滾動(當堆棧很大時)以瞭解上下文。我真的希望只是修改信息。 – elmotec

+0

@elmotec:我已經添加了「封裝在函數中」的解決方案。 – jfs

+0

回落到與@unubtu非常相似。不幸的是,我只能選擇一個答案,所以我選擇了這個答案,因爲它顯示了更改ex.args和異常鏈接。謝謝。 – elmotec

4

ex.args中的第一項始終是消息 - 如果有的話。 (注意一些例外,比如一個由assert False提出,ex.args是一個空的元組。)

我不知道修改消息不是重新分配一個新的元組ex.args一個更清潔的方式。 (我們不能修改元組,因爲元組是不可變的)。

下面的代碼是與你相似,除了它構造元組,而不使用中間表,它處理的情況下,當ex.args是空的,並以使代碼的可讀性,它隱藏上下文管理器內的樣板:

import contextlib 

def process(val): 
    with context(val): 
     do_something(val) 

def do_something(val): 
    # assert False 
    return 1/val 

@contextlib.contextmanager 
def context(msg): 
    try: 
     yield 
    except Exception as ex: 
     msg = '{}: {}'.format(msg, ex.args[0]) if ex.args else str(msg) 
     ex.args = (msg,) + ex.args[1:] 
     raise 

process(0) 

產生一個堆棧跟蹤與此作爲最終的消息:

ZeroDivisionError: 0: division by zero 
+0

這確實比我最初的嘗試更清晰。儘管它很冗長,但它完成了改變信息的簡單工作。謝謝。 – elmotec