0

可能重复:
引发异常时如何修改 Python 回溯对象?

考虑这个玩具示例:

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

即使错误的位置是 call 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()而不是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)

在这种情况下,错误也在于twice使用无效参数列表调用。因此,堆栈跟踪直接指向错误的位置(而不是指向底层 [最有可能的 C] 代码中第一次检测到错误的行,谢天谢地)。这是应该的,IMO 1

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


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

编辑:在原始帖子中强调了评论,但显然被前两个响应者忽略了。

4

4 回答 4

3

好吧,您可以更改twice为:

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

但是,当然,如果您要在 中放置一个 try/except 块twice,您不妨在那里进行实际检查。当然,如果您想在此处提出异常,尽管您有脚注,但您真正应该做的是。异常发生在它发生的地方。您永远不能调用函数并期望在该特定函数的实际主体中会发生任何异常。函数总是调用其他函数。拥有执行此类操作的验证器函数很好,但如果是这样,您只需要接受异常将在那里引发,而不是在其他地方引发。

作为对您的编辑的回应:当您在发生异常的地方引发异常时,您似乎正在“公开私有代码”,但以另一种方式进行操作会隐藏错误,这是更糟糕的方式。回溯很容易让您查看堆栈并查找调用函数的位置。但是,按照您的描述进行操作会将回溯切断得太高,从而无法查看错误实际发生的位置。如果代码可以在其他地方引发异常,错误代码很容易隐藏自己。

更一般地说,你所说的“错误的轨迹”是一条红鲱鱼。应在检测到异常情况时引发异常(可能是也可能不是“错误”)。“错误在哪里”是一个更复杂的问题,您不应该试图通过创建在其他地方引发自身的“意大利面条异常”来掩盖它。作为人类,您可以看到这是这个特定调用链中的“出错的地方”,但这并不意味着您应该尝试让您的程序变得聪明并让它解决这个问题。它可能在某些时候出错并引起更大的混乱。twice(None)

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

于 2012-09-12T03:57:07.503 回答
1

长答案简短:不编写 C 扩展或使用 ctypes 魔法,您无法操纵正常的 Python 回溯来假装错误来自raise代码中的语句以外的任何地方。

从 Python 引发的异常将raise在其回溯的最顶部有一条语句,因为根据定义,这是异常起源的地方——您无法更改它(没有任何上述技巧)。在您的情况下,您希望异常看起来像是来自函数调用(作为最顶层的框架),我说的是纯 Python 不可能。

如果您仍想查看 ctypes 或 CPython 方法来执行此操作,我可以向您展示。但是你不会通过坚持纯 Python 代码得到你想要的(而且你显然想要你想要的)。

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

由于 的存在generator.throw(),您还可以导致异常出现def在生成器函数或yield语句中,所有这些都来自 Python 代码。当然,两者都不会帮助你实现你想要的语义,但为了完整起见,我想提到这些。

于 2012-09-12T04:40:25.623 回答
1

请参阅引发异常时如何修改 Python 回溯对象?

在那里你会找到更多不这样​​做的建议,还有一个指向 jinja2 中一些丑陋代码的链接,如果你真的必须这样做,它会告诉你如何去做。

于 2012-09-12T05:06:24.010 回答
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
于 2012-09-12T03:56:45.773 回答