Python:检查异常抛出的位置

2 投票
2 回答
692 浏览
提问于 2025-04-16 07:43

看这段代码:

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 个回答

3

你无法在没有真正抛出异常的情况下决定异常会在哪里被处理。这一点在这里很明显:

try: 
    raise input('Raise which?')
except input('Catch which?') as e: 
    pass`

任何能够满足你需求的函数都必须预测用户的输入。这整个过程都是徒劳的,Python对此没有支持。

不过,我希望你只是出于好奇问这个问题……

2

这件事听起来有点傻,大多数人可能会说这是不可能的(THC4k对此有很有说服力的证据),但这听起来很有趣,而且在很多实际应用中应该是可以做到的。

第一步:你需要回溯到之前的调用帧。可以用 sys._getframeinspect.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_GLOBALLOAD_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 属性进行比较。当你找到匹配时,你就找到了你的函数。你还应该遍历模块中每个类的方法。可能会有处理发生在某个嵌套函数中的情况,这种情况下,我目前想不到合适的解决办法(这将是另一个字节码的黑客行为,可能会不稳定)。

第五步:获利。

这应该能给你一个大致的思路,告诉你该怎么做。虽然会有各种边缘情况让你无法做到,但在正常情况下,你应该能成功。如果你写的代码依赖于能够做到这一点,它肯定会出问题。这有点像“我能做到所以我去做”的感觉。

撰写回答