在Python中"取消捕获"异常

And*_*den 19 python exception-handling exception

我应该如何"重新抛出"异常,也就是说,假设:

  • 我在我的代码中尝试了一些东西,不幸的是它失败了.
  • 我尝试了一些"聪明"的解决方法,这次碰巧也失败了

如果我从(失败的)解决方法中抛出异常,那对用户来说会非常混乱,所以我认为最好重新抛出原始异常(?),并附带描述性回溯(关于实际的)问题)...

注意:这方面的激励示例是在调用时np.log(np.array(['1'], dtype=object)),它尝试一个诙谐的解决方法并给出一个AttributeError(它"真的"一个TypeError).

我能想到的一种方法就是重新调用有问题的函数,但这似乎是伪造的(理论上,原始函数可能会在第二次调用时发挥一些不同的行为):
好的,这是一个可怕的例子,但是...

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

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

def a():
    f()
Run Code Online (Sandbox Code Playgroud)

假设我这样做了:

try:
    a()
except:
    # attempt witty workaround
    g()
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-4-c76b7509b315> in <module>()
      3 except:
      4     # attempt witty workaround
----> 5     g()
      6

<ipython-input-2-e641f2f9a7dc> in g()
      4
      5 def g():
----> 6     raise Exception("coconut")
      7
      8

Exception: coconut
Run Code Online (Sandbox Code Playgroud)

嗯,问题根本不在于椰子,而是麻雀:

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
Run Code Online (Sandbox Code Playgroud)

有没有一种标准的方法可以解决这个问题,还是我认为它完全错了?

Jef*_*ner 9

如果您想让最终用户看到您从未调用过g(),那么您需要存储第一个错误的回溯,调用第二个函数,然后使用原始回溯抛出原始文件.(否则,在Python2中,裸加注重新引发第二个异常,而不是第一个异常).问题是没有2/3兼容的方法来引发回溯,因此你必须将Python 2版本包装在一个exec语句中(因为它是SyntaxErrorPython 3中的一个).

这是一个允许你这样做的功能(我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."""
)
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样使用它(为了清晰起见,我也更改了Exception类型).

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

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

def a():
    f()

try:
    a()
except TypeError as e:
    import sys
    # save the traceback from the original exception
    _, _, tb = sys.exc_info()
    try:
        # attempt witty workaround
        g()
    except:
        raise_with_traceback(e, tb)
Run Code Online (Sandbox Code Playgroud)

在Python 2中,您只能看到a()f():

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
Run Code Online (Sandbox Code Playgroud)

但是在Python 3中,它仍然注意到还有一个额外的异常,因为你在其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
Run Code Online (Sandbox Code Playgroud)

如果你绝对希望它看起来像g()Python 2和Python 3中都没有发生过异常,你需要先检查一下你是否超出了该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)
Run Code Online (Sandbox Code Playgroud)

这将在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
Run Code Online (Sandbox Code Playgroud)

而Python 3中的这个回溯:

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
Run Code Online (Sandbox Code Playgroud)

它确实添加了一个额外的非有用的回溯线,raise exc.with_traceback(traceback)向用户显示,但它相对干净.


mor*_*tar 8

这是一个完全疯狂的东西,我不确定会工作,但它在python 2和3中都有效.(但它确实需要将异常封装到一个函数中......)

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
Run Code Online (Sandbox Code Playgroud)

哪个是正确的异常和正确的堆栈跟踪,没有hackery.

>>> def g(): print "Worked around." # workaround is successful in this case

>>> tryhard()
Fail!
Worked around.

>>> def f(): print "Success!" # normal method works

>>> tryhard()
Success!
Run Code Online (Sandbox Code Playgroud)


msw*_*msw 6

Ian Bicking 在重新加注方面有一个很好的入门.

作为必然结果,我的规则是仅捕获代码知道如何处理的异常.实际上很少有方法符合这条规则.例如,如果我正在读取文件并且抛出了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]
Run Code Online (Sandbox Code Playgroud)

在python 3中,最终加注可以写成:

ei = exc_info[1]
ei.filname = exc_info[0]
ei.__traceback__ = exc_info[2]
raise ei
Run Code Online (Sandbox Code Playgroud)