Обработка исключений, когда ошибки могут возникать в основной программе или при очистке - PullRequest
5 голосов
/ 02 января 2012

Это с Python 2.6.6 (по умолчанию) в Debian Squeeze. Рассмотрим следующий код Python.

import sys
try:
    raise Exception("error in main")
    pass
except:
    exc_info = sys.exc_info()
finally:
    try:
        print "cleanup - always run"
        raise Exception("error in cleanup")
    except:
        import traceback
        print >> sys.stderr, "Error in cleanup"
        traceback.print_exc()
    if 'exc_info' in locals():
        raise exc_info[0], exc_info[1], exc_info[2]

print "exited normally"

Получена ошибка

Error in cleanup
Traceback (most recent call last):
  File "<stdin>", line 10, in <module>
Exception: error in cleanup
cleanup - always run
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
Exception: error in main

Идея состоит в том, чтобы справиться с ситуацией, когда какой-либо код или очистка этого кода (который всегда выполняется) или и того и другого выдает ошибку. Это обсуждается, например, Яном Бикингом в « Re-Raising Exception" . В конце этого поста (см. Update:) он описывает, как обрабатывать аналогичный случай кода + откат / возврат (выполняется только в случае ошибки).

Я возился с этим и придумал приведенный выше код, который немного чудовищен. В частности, если есть только ошибка в очистить (закомментировав raise Exception("error in main")), код по-прежнему нормально работает, хотя и выводит обратную трассировку. В настоящее время я даю приоритет ошибки, не связанной с очисткой, чтобы программа могла остановить программу.

В идеале я хотел бы, чтобы любая ошибка остановила программу, но это не так легко организовать. Кажется, что Python хочет вызвать только одну ошибку, потерять другие, если они есть, и по умолчанию это обычно последняя ошибка. Изменение порядка приводит к появлению сверток, как указано выше.

Также использование locals() немного уродливо. Можно ли сделать лучше?

EDIT: ответ srgerg познакомил меня с понятием контекстных менеджеров и ключевым словом with. В дополнение к PEP 343 , другие соответствующие биты документации, которые я нашел (в произвольном порядке). Типы диспетчера контекста , Оператор with и http://docs.python.org/reference/datamodel.html#context-managers. Это, безусловно, кажется большим улучшением предыдущих подходов к этому, то есть кода спагетти, включающего в себя попытки, исключения и окончание. .

Подводя итог, я хочу, чтобы такое решение дало мне две вещи.

  1. Возможность исключения в основном коде или в очистка, чтобы остановить программу в своих треках. Контекстные менеджеры делают это, потому что если тело цикла with имеет исключение, а тело exit не делает, тогда это исключение распространяется. Если exit выдает исключение, а тело цикла with - нет, тогда это распространяется. если оба выдают исключение, то exit Исключение распространяется и один из тела цикла while подавлено. Это все задокументировано, т.е. Типы диспетчера контекста ,

    contextmanager. выход (exc_type, exc_val, exc_tb)

    Выйдите из контекста времени выполнения и верните логический флаг, указывающий, должно ли подавляться любое возникшее исключение. [...] Возвращение истинного значения из этого метода приведет к тому, что оператор with подавит исключение и продолжит выполнение с заявление сразу же после заявления с. В противном случае исключение продолжает распространяться после завершения этого метода выполнения. Исключения, возникающие во время выполнения этого метода, заменят все исключения, возникшие в теле, на
    заявление. [...] Передаваемое исключение никогда не следует пересматривать явно. вместо этого этот метод должен возвращать ложное значение указывает, что метод завершен успешно и не хочет подавлять возникшее исключение.

  2. Если есть исключения в обоих местах, я хочу увидеть следы из обоих, даже если технически выдается только одно исключение. Это истина основана на экспериментах, потому что если оба выдают исключение, затем распространяется исключение exit , но трассировка от тела цикла while все еще печатается, как в ответ Сргерга . Тем не менее, я не могу найти это документально в любом месте, что неудовлетворительно.

Ответы [ 3 ]

4 голосов
/ 02 января 2012

В идеале вы должны использовать python с оператором для очистки в блоке try ... except, который будет выглядеть примерно так:

class Something(object):
    def __enter__(self):
        print "Entering"

    def __exit__(self, t, v, tr):
        print "cleanup - always runs"
        raise Exception("Exception occurred during __exit__")

try:
    with Something() as something:
        raise Exception("Exception occurred!")
except Exception, e:
    print e
    import traceback
    traceback.print_exc(e)

print "Exited normally!"

Когда я запускаю это, он печатает:

Entering
cleanup - always runs
Exception occurred during __exit__
Traceback (most recent call last):
  File "s3.py", line 11, in <module>
    raise Exception("Exception occurred!")
  File "s3.py", line 7, in __exit__
    raise Exception("Exception occurred during __exit__")
Exception: Exception occurred during __exit__
Exited normally!

Обратите внимание, что любое исключение прекратит работу программы и может быть рассмотрено в инструкции except.

Редактировать: В соответствии с документацией с оператором, связанной с вышеупомянутым, метод __exit__() должен вызывать исключение только в случае ошибки внутри __exit__(), то есть он не должен повторно вызывать в него передано исключение.

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

class Something(object):
    def __enter__(self):
        print "Entering"

    def __exit__(self, t, v, tr):
        print "cleanup - always runs"
        try:
            raise Exception("Exception occurred during __exit__")
        except Exception, e:
            if (t, v, tr) != (None, None, None):
                # __exit__ called with an existing exception
                return False
            else:
                # __exit__ called with NO existing exception
                raise

try:
    with Something() as something:
        raise Exception("Exception occurred!")
        pass
except Exception, e:
    print e
    traceback.print_exc(e)
    raise

print "Exited normally!"

Это печатает:

Entering
cleanup - always runs
Exception occurred!
Traceback (most recent call last):
  File "s2.py", line 22, in <module>
    raise Exception("Exception occurred!")
Exception: Exception occurred!
Traceback (most recent call last):
  File "s2.py", line 22, in <module>
   raise Exception("Exception occurred!")
Exception: Exception occurred!
1 голос
/ 02 января 2012

Аналогичное поведение можно получить, предоставив настраиваемый исключительный хук :

import sys, traceback

def excepthook(*exc_info):
    print "cleanup - always run"
    raise Exception("error in cleanup")
    traceback.print_exception(*exc_info)
sys.excepthook = excepthook

raise Exception("error in main")

Пример вывода:

cleanup - always run
Error in sys.excepthook:
Traceback (most recent call last):
  File "test.py", line 5, in excepthook
    raise Exception("error in cleanup")
Exception: error in cleanup

Original exception was:
Traceback (most recent call last):
  File "test.py", line 9, in <module>
    raise Exception("error in main")
Exception: error in main

В этом примере код работает следующим образом:

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

Примечание: я не нашел никакой документации относительно печати оригинального исключения, когда что-то не получается, но я видел такое поведение как в cpython, так и в jython. В частности, в cpython я видел следующую реализацию:

void
PyErr_PrintEx(int set_sys_last_vars)
{
    ...
    hook = PySys_GetObject("excepthook");
    if (hook) {
        ...
        if (result == NULL) {
            ...
            PySys_WriteStderr("Error in sys.excepthook:\n");
            PyErr_Display(exception2, v2, tb2);
            PySys_WriteStderr("\nOriginal exception was:\n");
            PyErr_Display(exception, v, tb);
            ...
        }
    }
}
0 голосов
/ 25 марта 2017

Вы были довольно близки к простому решению.Просто используйте traceback.print_exc () в первом исключении - тогда вам больше не нужно обрабатывать второе исключение.Вот как это может выглядеть:

error7 = False
try:
    raise Exception("error in main")
    pass
except:
    import traceback
    traceback.print_exc()
    error7 = True
finally:
    print "cleanup - always run"
    raise Exception("error in cleanup")
    if error7:
        raise SystemExit()

print "exited normally"

Информация о том, было ли выброшено исключение, хранится в error7, и, если это так, SystemExit() повышается в конце блока finally.

Вывод с включенными обоими операторами raise:

cleanup - always run
Traceback (most recent call last):
  File "G:/backed-up to mozy/Scripts/sandbox.py", line 3, in <module>
    raise Exception("error in main")
Exception: error in main
Traceback (most recent call last):

  File "<ipython-input-1-10089b43dd14>", line 1, in <module>
    runfile('G:/backed-up to mozy/Scripts/sandbox.py', wdir='G:/backed-up to mozy/Scripts')

  File "C:\Anaconda2\lib\site-packages\spyder\utils\site\sitecustomize.py", line 866, in runfile
    execfile(filename, namespace)

  File "C:\Anaconda2\lib\site-packages\spyder\utils\site\sitecustomize.py", line 87, in execfile
    exec(compile(scripttext, filename, 'exec'), glob, loc)

  File "G:/backed-up to mozy/Scripts/sandbox.py", line 11, in <module>
    raise Exception("error in cleanup")

Exception: error in cleanup
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...