提出看似来自来电者

时间:2017-09-16 04:44:33

标签: python exception exception-handling

我遇到的问题与here有问题但错误地关闭了 作为another related question的副本:

Python库如何以自己的方式引发异常 代码它没有暴露在追溯?动机就是成功 清除库函数调用不正确:违规 呼叫者中的一行似乎应该承担责任,而不是 图书馆内部(故意,正确地)提出的 异常。

正如伊恩对封闭式问题的评论所指出的,这是 与询问如何调整调用者中的代码进行更改相同 回溯的出现方式。

我的失败尝试如下。 在标记为QUESTION的行上,我尝试修改了属性 tb,例如tb.tb_frame = tb.tb_frame.f_back但这导致了 AttributeError: readonly attribute。我也试图创建一个 具有与tb相同属性的duck-typed对象,但在此期间失败 reraise()TypeError: __traceback__ must be a traceback or None。 (我试图通过子类化traceback来智胜这一点,但TypeError: type 'traceback' is not an acceptable base type已得到满足。

对于这个X,调整traceback对象本身可能是错误的Y - 也许还有其他策略?

假设Alice编写了以下库:

import sys

# home-made six-esque Python {2,3}-compatible reraise() definition
def reraise( cls, instance, tb=None ): # Python 3 definition
    raise ( cls() if instance is None else instance ).with_traceback( tb )
try: 
    Exception().with_traceback
except: # fall back to Python 2 definition
    exec( 'def reraise( cls, instance, tb=None ): raise cls, instance, tb' )
    # has to be wrapped in exec because this would be a syntax error in Python 3.0

def LibraryFunction( a ):
    if isinstance( a, (int, float) ):
        return a + 1
    else:
        err = TypeError( "expected int or float, got %r" % a )
        RaiseFromAbove( err )   # the traceback should NOT show this line
                                # because this function knows that it is operating
                                # correctly and that the caller is at fault

def RaiseFromAbove( exception, levels=1 ):
    # start by raising and immediately catching the exception
    # so that we can get a traceback from sys.exc_info()
    try:
        raise( exception )
    except:  
        cls, instance, tb = sys.exc_info()
        for i in range( levels + 1 ):
            pass # QUESTION: how can we manipulate tb here, to remove its deepest levels?
        reraise( cls, instance, tb )

现在,假设Alice发布了库,Bob下载了它。 Bob编写的代码调用它如下:

from AlicesLibrary import LibraryFunction

def Foo():
    LibraryFunction( 'invalid input' )  # traceback should reach this line but go no deeper

Foo()

关键在于,由于没有工作RaiseFromAbove的情况,回溯将显示异常来自Alice的库的第17行。因此,鲍勃(或那里的Bobs的一个重要子群)将通过电子邮件发送给爱丽丝说'嘿,你的代码在第17行被破坏了。"但事实上,LibraryFunction()确切知道它在发布异常时所做的工作。 Alice可以尽力重新说明异常,以尽可能清楚地表明库被错误地称为,但追溯会引起注意力。实际犯错的地方是Bob代码的第4行。此外,Alice的代码知道这个,因此不允许Alice的代码将责任分配到它所属的位置。因此,为了获得最大可能的透明度并减少支持流量,回溯应该不会超过Bob代码的第4行,没有 Bob必须自己编写此行为。

mattbornski提供了一个"你不应该想要这样做"回答here,我认为这一点很重要。当然,如果你说"那不是我的错,"转移责任,你不知道你必须把责任推到正确的地方。但是你知道你(LibraryFunction)已经努力对你所交付的输入参数进行显式类型检查,并且这项检查已成功(在某种意义上说支票本身没有引发异常),结果为负。可以肯定的是,Bob的代码可能不会出现错误"也许它没有产生无效的输入 - 也许Bob只是从其他地方传递了这个论点。但不同的是,他已经在没有检查的上传递了它。如果Bob努力进行检查,并且检查代码本身没有引发异常,那么Bob也可以随意RaiseFromAbove,从而帮助用户修改代码。

3 个答案:

答案 0 :(得分:1)

您可以像this answer中一样重新定义sys.excepthook函数:

import os
import sys
import traceback

def RaiseFromAbove( exception ):
    sys.excepthook = print_traceback
    raise( exception )

def print_traceback(exc_type, exc_value, tb):
    for i, (frame, _) in enumerate(traceback.walk_tb(tb)):
        # for example:
        if os.path.basename(frame.f_code.co_filename) == 'AlicesLibrary.py':
            limit = i
            break
    else:
        limit = None
    traceback.print_exception(exc_type, exc_value, tb, limit=limit, chain=False)

这需要用于walk_tb

的Python 3.5

答案 1 :(得分:1)

我认为你的X的Pythonic Y会改变你的代码,以便故障本身引发异常。在这种情况下,不是进行显式类型检查然后调用异常引发方法,而是练习EAFP和只需将1 添加到它们传入的任何内容中,如果它不起作用,就会引发错误。如果由于某种原因需要明确限制这两种类型,请将assert isinstance(a, (int, float)), "LibraryFunction() requires an int or float argument"添加到函数的开头,并让类型验证引发错误。如果Alice库中stacktrace底部的代码行不是异常的“真实”源,请不要试图隐藏代码行,重构代码本身,以便stacktrace告诉你实际发生了什么错误。这将使Alice的代码更容易维护,Bob可以将堆栈追回到他自己的代码中,以查看他的问题在哪里开始。

答案 2 :(得分:0)

对此问题没有好的/直接/权威的答案。我在“需要权威参考”类别下发放了奖金,最接近的是Martijn的评论,即:

  1. 明确地做到这一点的方法是改变追溯对象或生成一个新对象;
  2. 不能在纯Python中完成,但必须通过使用不受支持的API基础架构来完成;
  3. 这不值得。
  4. 我怀疑了。因此,除非/直到任何人能够真正提供对此方法不可能的权威性参考,否则我将在此处将其作为“已接受”的答案发布。

    但是我不接受它对Python来说不​​是一个值得的愿望清单项目。这个问题已经产生了相当多的“你不应该想要这样做”的情绪,我仍然不同意:

    • 当然,Bob 学会正确阅读追溯,但是让他更容易让他这样做是错误的 - 帮助Alice帮助他指导注意到正确的地方?鲍勃天真地接触爱丽丝并报告她的代码中的错误的情景是夸大(尽管可能)的例子,以明确这一点。更可能的是,他只会有一个不必要的2秒暂停,因为他认为“第17行的问题......哦,等等,不,来电者是问题”。但为什么不放过他,让编程用户体验更流畅?在我看来,Python的理念就是围绕消除这种摩擦。

    • 当然,任何推定的RaiseFromAbove 可以被爱丽丝滥用或以其他方式滥用,因此可能会使Bob更加困惑而不是更少。对我而言,这是一个虚假的争论,因为它同样适用于Alice可以做出的任何其他不明智的编码决策,实际上也适用于Python和其他语言中已经存在的许多强大功能。根据说明和安全警告,正确使用时,应根据其值判断一个尖锐工具的价值。

    无论如何,赏金期限即将来临,如果我什么都不做,我相信一半的赏金会回到最高投票的答案。目前这是Tore的,但对我而言,只需添加1并让Bob执行侦探工作的想法与我所驾驶的相反:这使得它看起来像更多就像Alice的代码中存在问题一样。鲍勃可能会从追踪问题的智力练习中成为更好的程序员,但他可能很匆忙,无论如何,通过这种逻辑,我们都会在裸机上进行编程。因此,我会将奖励奖励给yinnonsanders的答案,不是因为它是一个完整而令人满意的解决方案,而是因为它至少与问题的精神一致,并且可能在某些情况下有效。