10

我想为这样的异常添加上下文:

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]
4

2 回答 2

6

第一项ex.args始终是消息——如果有的话。(注意一些例外,例如由 引发的例外,assert Falseex.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
于 2013-07-16T13:31:22.077 回答
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设置属性的显式语法,请参阅Exception Chaining and Embedded Tracebacks__cause__


如果唯一的问题是您问题中消息修改代码的冗长;您可以将其封装在一个函数中:

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:]
于 2013-07-16T14:29:44.307 回答