2013-07-16 228 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