如何在堆栈中推送异常?

时间:2012-09-12 03:46:22

标签: python exception stack decorator stack-trace

  

可能重复:
  How can I modify a Python traceback object when raising an exception?

考虑这个玩具示例:

def twice(n):
    _validate_twice_args(n)
    return 2*n

def _validate_twice_args(n):
    if type(n) != int:
        raise TypeError('n must be an int')

twice(None)
--
Traceback (most recent call last):
  File "demo.py", line 9, in <module>
    twice(None)
  File "demo.py", line 2, in twice
    _validate_twice_args(n)
  File "demo.py", line 7, in _validate_twice_args
    raise TypeError('n must be an int')
TypeError: n must be an int

即使错误的基因座是调用twice(None),回溯指的是负责错误的人甚至都不知道的代码,(“_validate谁?我从未调用过!我甚至不知道它是什么!“),并且不必要地公开了应该”落后于API“的代码。 即使在没有像_validate_twice_args这样的“私有”辅助函数的情况下,为了响应错误的参数而打印的堆栈跟踪也会不必要地暴露内部代码,并且会掩盖错误的轨迹。

例如,如果一个内联_validate_twice_args的代码,则堆栈跟踪如下所示:

Traceback (most recent call last):
  File "demo.py", line 10, in <module>
    twice(None)
  File "demo.py", line 3, in twice
    raise TypeError('n must be an int')
TypeError: n must be an int

要了解此类错误的堆栈跟踪应该是什么样的,这是由非常相似的错误类型产生的错误,调用twice()而不是{ {1}},但是在控制传递给twice(None)之前由Python引发的:

twice

在这种情况下,错误在于使用无效参数列表调用Traceback (most recent call last): File "demo.py", line 9, in <module> twice() TypeError: twice() takes exactly 1 argument (0 given) 。因此,堆栈跟踪直接指向错误的轨迹(并且到最初检测到错误的基础[最可能是C]代码中的行,谢天谢地)。这是应该的,IMO 1

如何修改twice和/或twice_args,以便堆栈跟踪的最后一行引用错误调用_validate_twice_args的源代码行?


1 我意识到关于堆栈跟踪在这种情况下应该说什么的看法与我的不同,但这些哲学考虑并不是这个主题的主题。

在原帖中

编辑 强调 备注,但前两位响应者显然忽略了这些评论。

4 个答案:

答案 0 :(得分:3)

好吧,您可以将twice更改为:

def twice(n):
    try:
        _validate_twice_args(n)
    except Exception, e:
        raise e
    return 2*n

但是,当然,如果您要在twice中添加try / except块,您可以在那里进行实际检查。当然,如果你想在那里提出异常,你应该做什么,尽管你的脚注。发生异常时会发生异常。您永远不能调用函数并期望在该特定函数的实际主体中发生任何异常。函数始终调用其他函数。拥有像这样的东西的验证器函数是没关系的,但是如果是这样的话,你只需要接受异常将在那里提出而不是在其他地方。

响应您的编辑:当您在异常发生时引发异常时,您可能会“暴露私有代码”,但是以另一种方式执行此操作会隐藏错误,这更糟糕。回溯可以让您轻松查看 up 堆栈并查找调用该函数的位置。然而,按照你所描述的方法,会将追溯过高,使得无法查看错误实际发生的位置。如果代码可以在其他地方引发异常,那么错误的代码可以很容易地隐藏自己。

更一般地说,你所描述的“错误的轨迹”是一个红色的鲱鱼。应在检测到异常情况的位置引发异常(可能是或可能不是“错误”)。 “错误在哪里”是一个更复杂的问题,你不应该试图通过创建“意大利面条例外”来提出自己的问题。作为一个人,你可以看到twice(None)在这个特定的调用链中是“犯错误的地方”,但这并不意味着你应该试着让你的程序变得聪明并让它弄明白。它可能在某些时候出错并引起更大的混淆。

所以回答你所强调的:不,你不能这样做。引发异常时会引发异常。你不能在一个地方引发一个例外,并让它显示出来就好像是在其他地方引发的一样。

答案 1 :(得分:1)

长答案简短:无需编写C扩展或使用ctypes魔法,无法操纵普通的Python回溯,假装错误来自代码中的raise语句。

从Python引发的异常将在其回溯的最顶部有一个raise语句,因为根据定义,异常发生的地方 - 你不能改变它(没有任何上述欺骗)。在您的情况下,您希望异常看起来像是来自函数调用(作为最顶层的框架),并且我说它不可能来自纯Python。

如果你仍然希望看到ctypes或CPython这样做,我可以告诉你。但是你坚持使用纯Python代码就不会得到你想要的东西(你显然想要你想要的东西)。

编辑:好的,这不是100%准确。当然,您总是可以调用一些抛出异常的扩展方法 - 异常“来自”对扩展方法的调用。因此,如果您将twice作为扩展方法编写,则可以实现所需的确切语义(这就是我所说的“编写C扩展”)。

由于generator.throw()的存在,您还可以使异常看起来出现在生成器函数中的def行或yield语句中,所有这些都来自Python代码。当然,这两种方法都不会帮助您实现所需的语义,但我想提及这些语义的完整性。

答案 2 :(得分:1)

请参阅How can I modify a Python traceback object when raising an exception?

在那里你会发现更多的建议,不要这样做,还有一个链接到jinja2中的一些丑陋的代码,告诉你如果你真的必须这样做。

答案 3 :(得分:0)

您可以将代码修改为以下内容:

def twice(n):
    error = _validate_twice_args(n)
    if error:
        raise error
    return 2*n

def _validate_twice_args(n):
    if type(n) != int:
        return TypeError('n must be an int')
    return None

twice(None)

这样,错误从twice提升。不幸的是,它需要twice方法中的代码多于简单的方法调用。

Traceback (most recent call last):
  File "<module1>", line 28, in <module>
  File "<module1>", line 25, in main
  File "<module1>", line 16, in twice
TypeError: n must be an int