python中的“uncatch”异常

2024-04-29 19:19:59 发布

您现在位置:Python中文网/ 问答频道 /正文

我应该如何“重新抛出”一个例外,也就是说,假设:

  • 我在代码中尝试了一些东西,但不幸失败了。在
  • 我尝试了一些“聪明”的解决方法,但这次也失败了

如果我从(失败的)解决方案中抛出异常,对于用户来说这将是非常令人困惑的,所以我认为最好是重新抛出原始异常(?),通过描述性回溯,它附带了(关于实际问题)。。。在

注意:这方面的激励性示例是在调用np.log(np.array(['1'], dtype=object))时,它tries a witty workaround and gives an ^{}(它“真的”是一个TypeError)。在

我能想到的一种方法是重新调用有问题的函数,但这似乎很棘手(因为理论上,原始函数在第二次被调用时可能会产生一些不同的行为):
好吧,这是一个很糟糕的例子,但是这里。。。

def f():
    raise Exception("sparrow")

def g():
    raise Exception("coconut")

def a():
    f()

假设我这样做:

^{pr2}$

问题不在于椰子,而在于麻雀:

try:
    a()
except:
    # attempt witty workaround
    try:
        g()
    except:
        # workaround failed, I want to rethrow the exception from calling a()
        a() # ideally don't want to call a() again
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-4-e641f2f9a7dc> in <module>()
     19     except:
     20         # workaround failed, I want to rethrow the exception from calling a()
---> 21         a()  # ideally don't want to call a() again

<ipython-input-3-e641f2f9a7dc> in a()
      8
      9 def a():
---> 10     f()
     11
     12

<ipython-input-1-e641f2f9a7dc> in f()
      1 def f():
----> 2     raise Exception("sparrow")
      3
      4
      5 def g():

Exception: sparrow

有没有一个标准的方法来处理这个问题,或者我认为它是完全错误的?在


Tags: to方法ininputdefipythonnpexception
3条回答

这里有一些完全疯狂的东西,我不确定它是否能工作,但是它在python2和python3中都能工作。(但是,它需要将异常封装到函数中…)

def f():
    print ("Fail!")
    raise Exception("sparrow")
def g():
    print ("Workaround fail.")
    raise Exception("coconut")
def a():
    f()

def tryhard():
    ok = False
    try:
        a()
        ok = True
    finally:
        if not ok:
            try:
                g()
                return # "cancels" sparrow Exception by returning from finally
            except:
                pass

>>> tryhard()
Fail!
Workaround fail.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in tryhard
  File "<stdin>", line 2, in a
  File "<stdin>", line 3, in f
Exception: sparrow

这是正确的异常和正确的堆栈跟踪,并且没有黑客攻击。在

^{pr2}$

伊恩·比金有一个很好的primer on re-raising。在

作为推论,我的规则是只捕捉代码知道如何处理的异常。很少有方法真正符合这个规则。例如,如果我正在读取一个文件,并且抛出了一个IOException,那么这个方法几乎没有什么合理的功能。在

作为一个推论,如果您可以返回到一个良好的状态,并且不只是想将用户抛出,那么在“main”中捕捉异常是合理的;这只在交互式程序中获得。在

《初级读本》中的相关章节是最新的:

try:
    a()
except:
    exc_info = sys.exc_info()
    try:
        g()
    except:
        # If this happens, it clobbers exc_info,
        # which is why we had to save it above
        import traceback
        print >> sys.stderr, "Error in revert_stuff():"
        # py3 print("Error in revert_stuff():", file=sys.stderr)
        traceback.print_exc()
    raise exc_info[0], exc_info[1], exc_info[2]

在python3中,final raise could be written as

^{pr2}$

如果您想让最终用户看到您从未调用过g(),那么您需要存储第一个错误的回溯,调用第二个函数,然后将原始回溯与原始回溯一起抛出。(否则,在Python2中,bare raise会重新引发第二个异常,而不是第一个异常)。问题是没有2/3兼容的方法来引发回溯,所以您必须用exec语句包装Python2版本(因为它在Python3中是SyntaxError)。在

这里有一个函数可以让您这样做(我最近将它添加到pandas代码库中):

import sys
if sys.version_info[0] >= 3:
    def raise_with_traceback(exc, traceback=Ellipsis):
        if traceback == Ellipsis:
            _, _, traceback = sys.exc_info()
        raise exc.with_traceback(traceback)
else:
    # this version of raise is a syntax error in Python 3
    exec("""
def raise_with_traceback(exc, traceback=Ellipsis):
    if traceback == Ellipsis:
        _, _, traceback = sys.exc_info()
    raise exc, None, traceback
""")

raise_with_traceback.__doc__ = (
"""Raise exception with existing traceback.
If traceback is not passed, uses sys.exc_info() to get traceback."""
)

然后您可以这样使用它(为了清晰起见,我还更改了异常类型)。在

^{pr2}$

在Python2中,您只看到a()和{}:

Traceback (most recent call last):
  File "test.py", line 40, in <module>
    raise_with_traceback(e, tb)
  File "test.py", line 31, in <module>
    a()
  File "test.py", line 28, in a
    f()
  File "test.py", line 22, in f
    raise TypeError("sparrow")
TypeError: sparrow

但在python3中,它仍然注意到还有一个额外的异常,因为您在它的except子句中引发了这个异常[它颠倒了错误的顺序,并使用户更加困惑]:

Traceback (most recent call last):
  File "test.py", line 38, in <module>
    g()
  File "test.py", line 25, in g
    raise ValueError("coconut")
ValueError: coconut

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 40, in <module>
    raise_with_traceback(e, tb)
  File "test.py", line 6, in raise_with_traceback
    raise exc.with_traceback(traceback)
  File "test.py", line 31, in <module>
    a()
  File "test.py", line 28, in a
    f()
  File "test.py", line 22, in f
    raise TypeError("sparrow")
TypeError: sparrow

如果您绝对希望它看起来像是在Python 2和Python 3中从未发生过g()异常,那么您需要首先检查您是否退出了except子句:

try:
    a()
except TypeError as e:
    import sys
    # save the traceback from the original exception
    _, _, tb = sys.exc_info()
    handled = False
    try:
        # attempt witty workaround
        g()
        handled = True
    except:
        pass
    if not handled:
        raise_with_traceback(e, tb)

这将在Python 2中实现以下回溯:

Traceback (most recent call last):
  File "test.py", line 56, in <module>
    raise_with_traceback(e, tb)
  File "test.py", line 43, in <module>
    a()
  File "test.py", line 28, in a
    f()
  File "test.py", line 22, in f
    raise TypeError("sparrow")
TypeError: sparrow

Python3中的回溯:

Traceback (most recent call last):
  File "test.py", line 56, in <module>
    raise_with_traceback(e, tb)
  File "test.py", line 6, in raise_with_traceback
    raise exc.with_traceback(traceback)
  File "test.py", line 43, in <module>
    a()
  File "test.py", line 28, in a
    f()
  File "test.py", line 22, in f
    raise TypeError("sparrow")
TypeError: sparrow

它确实添加了一个额外的无用的回溯行,向用户显示raise exc.with_traceback(traceback),但相对来说是干净的。在

相关问题 更多 >