Подтвердить что ты не робот

"Отключение" исключения в python

Как я должен "перебросить" исключение, то есть предположим:

  • Я пытаюсь что-то в своем коде, и, к сожалению, он терпит неудачу.
  • Я пробую какое-то "умное" обходное решение, которое также не срабатывает на этот раз.

Если я выброшу исключение из (обходного) обходного пути, он будет довольно запутанным для пользователя, поэтому я думаю, что лучше всего восстановить исходное исключение (?), с описательной трассировкой, с которой он приходит ( о реальной проблеме)...

Примечание: мотивирующим примером этого является вызов np.log(np.array(['1'], dtype=object)), где он пытается остроумный обходной путь и дает AttributeError (это действительно" a TypeError).

Один из способов, о котором я могу думать, - это просто повторить вызов оскорбительной функции, но это кажется упрямым (теоретически исходная функция может проявлять другое поведение во второй раз, когда она называлась):
Хорошо, это один ужасный пример, но здесь идет...

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

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

def a():
    f()

Предположим, что я сделал это:

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

Ну, проблема на самом деле не связана с кокосовым орехом, но воробей:

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

Есть ли стандартный способ справиться с этим, или я думаю об этом совершенно неправильно?

4b9b3361

Ответ 1

Если вы хотите, чтобы конечный пользователь показывал, что вы никогда не звонили g(), вам нужно сохранить трассировку с первой ошибки, вызвать вторую функцию и затем выбросить оригинал с исходной трассировкой. (иначе, в Python2, голый рейз повторяет второе исключение, а не первое). Проблема в том, что не существует совместимого с 2/3 способом с трассировкой, поэтому вам нужно обернуть версию Python 2 в инструкции exec (так как это a SyntaxError в Python 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."""
)

И тогда вы можете использовать его так (я также изменил типы исключений для ясности).

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)

И в 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

Но в 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

Если вы абсолютно хотите, чтобы это выглядело как 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)

Что дает вам следующую трассировку в 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

И эта трассировка в 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

Он добавляет дополнительную непригодную строку трассировки, которая показывает пользователю raise exc.with_traceback(traceback), но она относительно чистая.

Ответ 2

Вот что-то совершенно ореховое, что я не был уверен, что это сработает, но оно работает как в 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

Какое правильное исключение и правильная трассировка стека и без хакера.

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

>>> tryhard()
Fail!
Worked around.

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

>>> tryhard()
Success!

Ответ 3

Ian Bicking имеет хороший праймер при повторном поднятии.

В качестве следствия мое правило состоит в том, чтобы ловить только исключения, которые код знает, как справиться. Очень мало методов на самом деле попали в это правило. Например, если я читаю файл и генерируется исключение IOException, это очень мало подходит для этого метода.

В качестве следствия этого, исключения catch в "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]

В python 3 окончательный рейз можно записать как:

ei = exc_info[1]
ei.filname = exc_info[0]
ei.__traceback__ = exc_info[2]
raise ei

Ответ 4

В Python 3 (специально протестировано на 3.3.2) все это работает лучше, нет необходимости сохранять sys.exc_info. Не переустанавливайте исходное исключение во втором обработчике исключений. Просто отметьте, что вторая попытка потерпела неудачу и поднимет оригинал в области исходного обработчика, например:

#!python3

try:
    a()
except Exception:
    g_failed = False
    try:
        g()
    except Exception:
        g_failed = True
    raise

Выход Python 3 правильно поднимает "воробей" и показывает трассировку через a() и f():

Traceback (most recent call last):
  File "x3.py", line 13, in <module>
    a()
  File "x3.py", line 10, in a
    f()
  File "x3.py", line 4, in f
    raise Exception("sparrow")
Exception: sparrow

Однако тот же самый script на Python 2 неправильно поднимает "кокос" и показывает только g():

Traceback (most recent call last):
  File "x3.py", line 17, in <module>
    g()
  File "x3.py", line 7, in g
    raise Exception("coconut")
Exception: coconut

Ниже приведены изменения, позволяющие сделать Python 2 корректным:

#!python2
import sys

try:
    a()
except Exception:
    exc = sys.exc_info()
    try:
        g()
    except Exception:
        raise exc[0], exc[1], exc[2] # Note doesn't care that it is nested.

Теперь Python 2 правильно показывает "воробей" и обе a() и f() traceback:

Traceback (most recent call last):
  File "x2.py", line 14, in <module>
    a()
  File "x2.py", line 11, in a
    f()
  File "x2.py", line 5, in f
    raise Exception("sparrow")
Exception: sparrow

Ответ 5

Захватите ошибку в своем предложении except, а затем вручную переподнимите ее позже. Захватите трассировку и перепечатайте ее с помощью модуля traceback.

import sys
import traceback

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

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

def a():
    f()

try:
    print "trying a"
    a()
except Exception as e:
    print sys.exc_info()
    (_,_,tb) = sys.exc_info()
    print "trying g"
    try:
        g()
    except:
        print "\n".join(traceback.format_tb(tb))
        raise e

Ответ 6

В Python 3, внутри функции, это можно сделать очень легко, следуя ответу от @Mark Tolonen, который использует логическое значение. Вы не можете сделать это за пределами функции, потому что нет возможности вырваться из внешнего выражения try: функция нужна для return.

#!python3

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

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

def a():
    f()

def h():
    try:
        a()
    except:
        try:
            g()
            return  # Workaround succeeded!
        except:
            pass  # Oh well, that didn't work.
        raise  # Re-raises *first* exception.

h()

Это приводит к:

Traceback (most recent call last):
  File "uc.py", line 23, in <module>
    h()
  File "uc.py", line 14, in h
    a()
  File "uc.py", line 10, in a
    f()
  File "uc.py", line 4, in f
    raise Exception("sparrow")
Exception: sparrow

... и если вместо g преуспеть:

def g(): pass

... тогда это не вызывает исключения.

Ответ 7

try:
    1/0  # will raise ZeroDivisionError
except Exception as first:
    try:
        x/1  # will raise NameError
    except Exception as second:
        raise first  # will re-raise ZeroDivisionError