Finding out an exception context
tlndr:如何在函数中判断是否从
我使用python 2.7并尝试为我的自定义异常类提供类似于py3的
1 2 3 4 5 6 | 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) |
这似乎工作正常:
1 2 3 4 5 6 | try: 1/0 except: raise MyErr('bang!') #>__main__.MyErr: ('bang!',) from ZeroDivisionError('integer division or modulo by zero',) |
有时我需要在异常块之外引发
1 2 3 | raise MyErr('just so') #>__main__.MyErr: ('just so',) from None |
但是,如果在此点之前存在处理的异常,则将其错误地设置为
1 2 3 4 5 6 7 8 9 | 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 |
我想原因是
This function returns a tuple of three values that give information about the exception that is currently being handled. <...> Here,"handling an exception" is defined as"executing or having executed an except clause."
所以,我的问题是:如何判断解释器是否正在执行
我的应用程序不可移植,欢迎任何Cpython特定的黑客攻击。
这是使用CPython 2.7.3测试的:
1 2 3 | $ python myerr.py MyErr('bang!',) from ZeroDivisionError('integer division or modulo by zero',) MyErr('nobang!',) |
只要在except子句的范围内直接创建魔术异常,它就可以工作。但是,一些额外的代码可以解除这个限制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 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 |
请记住,"明确比隐含更好",所以如果你问我这会更好:
1 2 3 4 | try: … except Exception as exc: raise MyErr("msg", cause=exc) |
以下方法可能有效,尽管它有点啰嗦。
-
从
import inspect; inspect.currentframe().f_code 获取当前帧的代码 -
检查字节码(
f_code.co_code ),可能使用dis.dis ,以确定帧是否在except 块中执行。 - 根据您想要做的事情,您可能希望返回一个框架,看看它是否未从except块调用。
例如:
1 2 3 4 5 6 7 | def infoo(): raise MyErr("from foo in except") try: nope except: infoo() |
-
如果没有任何帧位于
except 块中,则sys.exc_info() 已过时。
我搜索了Python源代码,看看是否有一些指针在进入
我发现这个
1 2 3 4 5 6 | enum fblocktype { LOOP, EXCEPT, FINALLY_TRY, FINALLY_END }; struct fblockinfo { enum fblocktype fb_type; basicblock *fb_block; }; |
A frame block is used to handle loops, try/except, and try/finally.
It's called a frame block to distinguish it from a basic block in the
compiler IR.
然后当你向上看时,会有一个基本块的描述:
Each basicblock in a compilation unit is linked via b_list in the
reverse order that the block are allocated. b_list points to the next
block, not to be confused with b_next, which is next by control flow.
还在这里阅读有关控制流图的更多信息:
A control flow graph (often referenced by its acronym, CFG) is a
directed graph that models the flow of a program using basic blocks
that contain the intermediate representation (abbreviated"IR", and in
this case is Python bytecode) within the blocks. Basic blocks
themselves are a block of IR that has a single entry point but
possibly multiple exit points. The single entry point is the key to
basic blocks; it all has to do with jumps. An entry point is the
target of something that changes control flow (such as a function call
or a jump) while exit points are instructions that would change the
flow of the program (such as jumps and ‘return’ statements). What this
means is that a basic block is a chunk of code that starts at the
entry point and runs to an exit point or the end of the block.
所有这些似乎表明Python设计中的框架块被视为临时对象。除了作为包含基本块的字节代码的一部分之外,它不直接包含在控制流图中,因此如果不解析帧字节代码,它似乎无法查询。
此外,我认为
sys.exc_info()
This function returns a tuple of three values that give information
about the exception that is currently being handled. The information
returned is specific both to the current thread and to the current
stack frame. If the current stack frame is not handling an exception,
the information is taken from the calling stack frame, or its caller,
and so on until a stack frame is found that is handling an exception.
Here,"handling an exception" is defined as"executing or having
executed an except clause." For any stack frame, only information
about the most recently handled exception is accessible.
因此,当它表示堆栈帧时,我认为它特别意味着基本块,并且所有"处理异常"谈话意味着帧块中的异常(例如
一种解决方案是在处理异常后调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import sys 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) try: print xxx except Exception as e: # exception handled sys.exc_clear() raise MyErr('look out') |
得到:
1 2 3 4 | Traceback (most recent call last): File"test.py", line 18, in <module> raise MyErr('look out')` __main__.MyErr: ('look out',) from None |
如果在没有提升