tlndr:如何在函数中告诉它是否从except
块(直接/间接)调用。 python2.7 / CPython的。
我使用python 2.7并尝试为我的自定义异常类提供与py3的__context__
类似的东西:
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:
1/0
except:
raise MyErr('bang!')
#>__main__.MyErr: ('bang!',) from ZeroDivisionError('integer division or modulo by zero',)
有时我需要在异常块之外引发MyErr
。这也很好:
raise MyErr('just so')
#>__main__.MyErr: ('just so',) from None
但是,如果在此之前存在处理的异常,则会将其错误地设置为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
我想原因是sys.exc_info
只返回“last”而不是“current”异常:
此函数返回三个值的元组,这些值提供有关当前正在处理的异常的信息。 &LT; ...&GT;这里,“处理异常”被定义为“执行或已执行一个except子句。”
所以,我的问题是:如何判断解释器是否执行except
子句(并且过去没有执行 )。换句话说:有没有办法知道MyErr.__init__
堆栈中是否有except
?
我的应用程序不可移植,欢迎任何Cpython特定的黑客攻击。
答案 0 :(得分:10)
使用CPython 2.7.3进行测试:
$ python myerr.py
MyErr('bang!',) from ZeroDivisionError('integer division or modulo by zero',)
MyErr('nobang!',)
只要在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
请记住,“明确比隐含更好”,所以如果你问我这会更好:
try:
…
except Exception as exc:
raise MyErr("msg", cause=exc)
答案 1 :(得分:3)
以下方法可能会奏效,虽然它有点啰嗦。
import inspect; inspect.currentframe().f_code
f_code.co_code
),可能使用dis.dis
,以确定帧是否在except
块中执行。例如:
def infoo():
raise MyErr("from foo in except")
try:
nope
except:
infoo()
except
块中没有任何一个框架,则sys.exc_info()
已过时。答案 2 :(得分:1)
一个解决方案是在处理异常后调用sys.exc_clear()
:
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')
给出:
Traceback (most recent call last):
File "test.py", line 18, in <module>
raise MyErr('look out')`
__main__.MyErr: ('look out',) from None
如果在没有提出MyErr
的情况下处理异常的地方不多,则可能更适合修改对MyErr
的调用,提供一些构造函数参数,甚至显式处理跟踪保留,如{{ 3}}
答案 3 :(得分:1)
我搜索了Python源代码,看看是否有一些指针在输入except
块时被设置,可以通过从自定义异常的构造函数中查看帧序列来查询。
我发现这个fblocktype
枚举存储在fblockinfo
结构中:
enum fblocktype { LOOP, EXCEPT, FINALLY_TRY, FINALLY_END };
struct fblockinfo {
enum fblocktype fb_type;
basicblock *fb_block;
};
fblocktype
上方有一条评论,描述框架块:
帧块用于处理循环,try / except和try / finally。 它被称为一个框架块,以区别于它中的基本块 编译器IR。
然后当你向上看时有一个基本块的描述:
编译单元中的每个基本块都通过b_list链接 块被分配的逆序。 b_list指向下一个 阻止,不要与控制流接下来的b_next混淆。
此处还有关于Control Flow Graphs:
的更多内容控制流程图(通常由其首字母缩略词CFG引用)是a 有向图使用基本块对程序流进行建模 包含中间表示(缩写为“IR”,并在 这种情况是块内的Python字节码。基本块 它们本身就是一块具有单一入口点的IR 可能有多个退出点。单一入口点是关键 基本块;这一切都与跳跃有关。一个切入点是 改变控制流的东西的目标(例如函数调用 或者跳转)而退出点是可以改变的指令 程序流程(如跳转和'返回'语句)。这是什么 意思是基本块是从一开始的一大块代码 入口点并运行到出口点或块的末尾。
所有这些似乎表明Python设计中的框架块被视为临时对象。它不直接包含在控制流图中,除非作为包含基本块的字节代码的一部分,所以如果不解析帧字节代码,它似乎无法查询。
此外,我认为示例中sys.exc_info
显示try
块异常的原因是因为它存储了当前基本块的最后一个异常,这里不考虑框架块。
<强> sys.exc_info()强>
此函数返回三个提供信息值的元组 关于当前正在处理的异常。信息 返回特定于当前线程和当前线程 堆栈框架。如果当前堆栈帧没有处理异常, 信息来自调用堆栈帧或其调用者, 依此类推,直到找到处理异常的堆栈帧。 这里,“处理异常”被定义为“执行或拥有” 执行了一个except子句。“对于任何堆栈帧,只有信息 关于最近处理的异常是可以访问的。
所以,当它说堆栈帧时,我认为它具体意味着基本块,所有“处理异常”谈话意味着帧块中的异常,例如try/except
,for
等,冒充上面的基本块。