"Отключение" исключения в 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
Есть ли стандартный способ справиться с этим, или я думаю об этом совершенно неправильно?
Ответы
Ответ 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