如何在Python中为异常添加上下文

时间:2013-07-16 13:20:57

标签: python exception

我想将上下文添加到这样的异常中:

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]

2 个答案:

答案 0 :(得分: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

答案 1 :(得分: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)

在您的情况下,您应该使用在新异常上设置__cause__属性的显式raise Error(key) from ex语法,请参阅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:]