找出异常上下文

geo*_*org 15 python exception python-2.7

tlndr:如何在函数中告诉它是否从except块(直接/间接)调用.python2.7/CPython的.

我使用python 2.7并尝试__context__为我的自定义异常类提供类似于py3的东西:

class MyErr(Exception):
    def __init__(self, *args):
        Exception.__init__(self, *args)
        self.context = sys.exc_info()[1]
    def __str__(self):
        return repr(self.args) + ' from ' + repr(self.context)
Run Code Online (Sandbox Code Playgroud)

这似乎工作正常:

try:
   1/0
except:
   raise MyErr('bang!')

#>__main__.MyErr: ('bang!',) from ZeroDivisionError('integer division or modulo by zero',)
Run Code Online (Sandbox Code Playgroud)

有时我需要MyErr在异常块之外被提升.这也很好:

raise MyErr('just so')

#>__main__.MyErr: ('just so',) from None
Run Code Online (Sandbox Code Playgroud)

但是,如果在此点之前存在处理的异常,则将其错误地设置为以下语境MyErr:

try:
    print xxx
except Exception as e:
    pass

# ...1000 lines of code....
raise MyErr('look out')

#>__main__.MyErr: ('look out',) from NameError("name 'xxx' is not defined",) <-- BAD
Run Code Online (Sandbox Code Playgroud)

我想原因是 sys.exc_info只返回"last"而不是"current"异常:

此函数返回三个值的元组,这些值提供有关当前正在处理的异常的信息.<...>这里,"处理异常"被定义为"执行或执行了except子句".

所以,我的问题是:如何判断的解释执行的except条款(而不是它过去执行).换句话说:有没有办法知道堆栈中MyErr.__init__是否存在exceptup?

我的应用程序不可移植,欢迎任何Cpython特定的黑客攻击.

jhe*_*ann 10

这是使用CPython 2.7.3测试的:

$ python myerr.py 
MyErr('bang!',) from ZeroDivisionError('integer division or modulo by zero',)
MyErr('nobang!',)
Run Code Online (Sandbox Code Playgroud)

只要在except子句的范围内直接创建魔术异常,它就可以工作.但是,一些额外的代码可以解除这个限制.

import sys
import opcode

SETUP_EXCEPT = opcode.opmap["SETUP_EXCEPT"]
SETUP_FINALLY = opcode.opmap["SETUP_FINALLY"]
END_FINALLY = opcode.opmap["END_FINALLY"]

def try_blocks(co):
    """Generate code positions for try/except/end-of-block."""
    stack = []
    code = co.co_code
    n = len(code)
    i = 0
    while i < n:
        op = ord(code[i])
        if op in (SETUP_EXCEPT, SETUP_FINALLY):
            stack.append((i, i + ord(code[i+1]) + ord(code[i+2])*256))
        elif op == END_FINALLY:
            yield stack.pop() + (i,)
        i += 3 if op >= opcode.HAVE_ARGUMENT else 1

class MyErr(Exception):
    """Magic exception."""

    def __init__(self, *args):
        callee = sys._getframe(1)
        try:
            in_except = any(i[1] <= callee.f_lasti < i[2] for i in try_blocks(callee.f_code))
        finally:
            callee = None

        Exception.__init__(self, *args)
        self.cause = sys.exc_info()[1] if in_except else None

    def __str__(self):
        return "%r from %r" % (self, self.cause) if self.cause else repr(self)

if __name__ == "__main__":
    try:
        try:
            1/0
        except:
            x = MyErr('bang!')
            raise x
    except Exception as exc:
        print exc

    try:
        raise MyErr('nobang!')
    except Exception as exc:
        print exc
    finally:
        pass
Run Code Online (Sandbox Code Playgroud)

请记住,"明确比隐含更好",所以如果你问我这会更好:

try:
    …
except Exception as exc:
    raise MyErr("msg", cause=exc)
Run Code Online (Sandbox Code Playgroud)