Python:检查异常抛出的位置
看这段代码:
def A():
try:
B()
except Exception:
pass
def B():
C()
def C():
print exception_handling_pointer()
A()
这个函数 exception_handling_pointer
应该返回一个指针,指向第一个检查这个特定异常的函数,也就是说,在这个例子中,我希望输出的结果类似于:
<function A ...>
我该如何实现这个函数 exception_handling_pointer
呢?
2 个回答
你无法在没有真正抛出异常的情况下决定异常会在哪里被处理。这一点在这里很明显:
try:
raise input('Raise which?')
except input('Catch which?') as e:
pass`
任何能够满足你需求的函数都必须预测用户的输入。这整个过程都是徒劳的,Python对此没有支持。
不过,我希望你只是出于好奇问这个问题……
这件事听起来有点傻,大多数人可能会说这是不可能的(THC4k对此有很有说服力的证据),但这听起来很有趣,而且在很多实际应用中应该是可以做到的。
第一步:你需要回溯到之前的调用帧。可以用 sys._getframe
或 inspect.currentframe
来获取第一个帧(别告诉别人,第二个似乎是第一个的别名)。然后你可以通过 f.f_back
来遍历这些帧。
第二步:每个帧都有一个 f.f_lasti
指令。这是该帧中最后执行的指令。你需要保存这个信息。接下来,向回查看字节码 - f.f_code.co_code
- 找到一个 SETUP_EXCEPT
操作码,它的参数指向 f.f_lasti 之后的位置。这个跳转点就是异常处理的地方。
第三步:这一步就有点复杂了。关键是,实际的比较操作会是一个 COMPARE_OP
,它的参数是 10。在我见过的所有情况下,它后面都会跟着一个 POP_JUMP_IF_FALSE
。这个指令会跳转到下一个 except
子句或 finally
子句。它前面会有代码将异常加载到栈上。如果只有一个异常,那就是简单的 LOAD_GLOBAL
或 LOAD_FAST
(取决于异常模块是全局的还是局部的),然后是 LOAD_ATTR
。如果有多个异常需要匹配,那么会有一系列的加载操作,后面跟着 BUILD_TUPLE
(常见情况)或 BUILD_LIST
(一些其他奇怪或不常见的情况)。
重点是,你可以通过 LOAD_X
指令来比较名称与正在匹配的异常。请注意,你只是 比较名称。如果他们重新分配了名称,那就麻烦了。
第四步:假设你找到了匹配的异常。现在你需要函数对象。我想到的最好方法是这样的:f.f_code
会有一个 co_filename
属性。你可以遍历 sys.modules
,每个模块都有一个 __name__
属性。你可以比较这两个,记得用 __name__.endswith(co_filename)
。当你找到匹配时,可以遍历模块的函数,并将它们的 f.func_code.co_firstlineno
属性与帧的 f.f_lineno
属性进行比较。当你找到匹配时,你就找到了你的函数。你还应该遍历模块中每个类的方法。可能会有处理发生在某个嵌套函数中的情况,这种情况下,我目前想不到合适的解决办法(这将是另一个字节码的黑客行为,可能会不稳定)。
第五步:获利。
这应该能给你一个大致的思路,告诉你该怎么做。虽然会有各种边缘情况让你无法做到,但在正常情况下,你应该能成功。如果你写的代码依赖于能够做到这一点,它肯定会出问题。这有点像“我能做到所以我去做”的感觉。