2009-03-30 6 views
72

Я пишу модуль и хочу иметь единую иерархию исключений для исключений, которые она может поднять (например, наследование от абстрактного класса для всех foo исключительные исключения модуля). Это позволяет пользователям модуля улавливать те особые исключения и обрабатывать их отчетливо, если это необходимо. Но многие из исключений, поднятых из модуля, возникают из-за какого-то другого исключения; например при выполнении какой-либо задачи из-за OSError в файле.Исключить повторное возбуждение с другим типом и сообщением, сохраняя существующую информацию

«обернуть» исключение, пойманное таким образом, чтобы оно имело другой тип и сообщение, чтобы информация была доступна в дальнейшем по иерархии распространения независимо от того, что улавливает исключение. Но я не хочу потерять существующий тип, сообщение и трассировку стека; это вся полезная информация для тех, кто пытается отладить проблему. Обработчик исключений верхнего уровня не подходит, поскольку я пытаюсь украсить исключение, прежде чем он продвигается дальше по стеку распространения, а обработчик верхнего уровня слишком поздно.

Это частично решается путем получения конкретных типов исключений моего модуля foo из существующего типа (например, class FooPermissionError(OSError, FooError)), но это не облегчает перенос существующего экземпляра исключения в новый тип или изменение сообщение.

Python's PEP 3134 «Цепочки исключений и встроенные трассировки» обсуждает изменение, принятое в Python 3.0 для «цепочки» объектов исключения, чтобы указать, что во время обработки существующего исключения было создано новое исключение.

То, что я пытаюсь сделать, связано: мне нужно, чтобы он также работал в более ранних версиях Python, и мне это нужно не для цепочки, а только для полиморфизма. Каков правильный способ сделать это?

+0

Исключения уже полностью полиморфный - все они являются подклассами Исключения. Что ты пытаешься сделать? «Различное сообщение» довольно тривиально с обработчиком исключений верхнего уровня. Почему вы меняете класс? –

+0

Как объясняется в вопросе (теперь, спасибо за ваш комментарий): Я пытаюсь украсить исключение, которое я поймал, чтобы он мог распространяться дальше с большей информацией, но не теряя. Слишком поздно обработчик верхнего уровня. – bignose

+0

Пожалуйста, взгляните на мой класс [CausedException] (http://code.activestate.com/recipes/578252-python-exception-chains-or-trees/?in=user-4182236), который может делать то, что вы хотите Python 2.x. Также в Python 3 это может быть полезно, если вы хотите предоставить более одного исходного исключения в качестве причины вашего исключения. Возможно, это соответствует вашим потребностям. – Alfe

ответ

79

Python 3 введены исключение цепочки (как описано в PEP 3134).Это позволяет повысить исключение, ссылаясь на существующее исключение как «причина»:

try: 
    frobnicate() 
except KeyError as exc: 
    raise ValueError("Bad grape") from exc 

Пойманная исключение, таким образом, становится частью (является «причиной») нового исключения, и доступно для любой код ловит новое исключение.


В Python 2, по-видимому, этот случай использования не имеет хорошего ответа (как описано Ian Bicking и Ned Batchelder). Облом.

+2

Разве Ian Bicking не описывает мое решение? Я сожалею, что дал такой честный ответ, но это странно, что это принято. –

+0

@bignose: Спасибо, что упомянули об этом. Я пропустил это изменение в Python3. Цепочка исключений сэкономит много горя. –

+1

@bignose Вы получили мою точку зрения не только от права, но и для использования «frobnicate» :) –

8

Вы можете создать свой собственный тип исключения, который расширяет whichever exception, который вы поймали.

class NewException(CaughtException): 
    def __init__(self, caught): 
     self.caught = caught 

try: 
    ... 
except CaughtException as e: 
    ... 
    raise NewException(e) 

Но большую часть времени, я думаю, было бы проще поймать исключение, обрабатывать его, и либо raise оригинальное исключение (и сохранить отслеживающий) или raise NewException(). Если бы я вызывал ваш код, и я получил одно из ваших особых исключений, я бы ожидал, что ваш код уже обработал все исключения, которые вы должны были поймать. Таким образом, мне не нужно обращаться к нему самостоятельно.

Редактировать: Я нашел this analysis способов выбросить собственное исключение и сохранить исходное исключение. Нет хороших решений.

+1

Вариант использования, который я описал, не предназначен для * обработки * исключения; это особенно касается * не * обработки, но добавления дополнительной информации (дополнительного класса и нового сообщения), чтобы он мог обрабатываться в стеке вызовов. – bignose

27

Вы можете использовать sys.exc_info(), чтобы получить трассировку, и поднять новое исключение с указанной трассировкой (как упоминает PEP). Если вы хотите сохранить старый тип и сообщение, вы можете сделать это в случае исключения, но это полезно только в том случае, если вы его поймаете.

Например

import sys 

def failure(): 
    try: 1/0 
    except ZeroDivisionError, e: 
     type, value, traceback = sys.exc_info() 
     raise ValueError, ("You did something wrong!", type, value), traceback 

Конечно, это действительно не так уж полезно. Если бы это было так, нам не понадобился бы этот PEP. Я бы не рекомендовал это делать.

+0

Devin, вы храните ссылку на трассировку там, не должны ли вы явно удалять эту ссылку? – Arafangion

+2

Я ничего не хранил, я оставил трассировку как локальную переменную, которая предположительно выходит за рамки. Да, возможно, что это не так, но если вы делаете исключения, подобные этим в глобальной области действия, а не внутри функций, у вас возникают большие проблемы. Если ваша жалоба заключается только в том, что она может быть выполнена в глобальном масштабе, правильным решением является не добавление неуместного шаблона, который должен быть объяснен и не имеет отношения к 99% использования, но для перезаписи решения, чтобы такая вещь не была необходимо, хотя и кажется, что ничего не изменилось, как я уже сделал. –

+3

Arafangion может ссылаться на предупреждение в [документации Python для 'sys.exc_info()'] (http://docs.python.org/library/sys.html#sys.exc_info), @Devin. В нем говорится: «Присвоение возвращаемого значения трассировки локальной переменной в функции, обрабатывающей исключение, вызовет циклическую ссылку». Тем не менее, в следующем примечании говорится, что с Python 2.2 цикл можно очистить, но более эффективно его избегать. –

-2

Наиболее straighforward решение для ваших нужд должно быть таким:

try: 
    upload(file_id) 
except Exception as upload_error: 
    error_msg = "Your upload failed! File: " + file_id 
    raise RuntimeError(error_msg, upload_error) 

Таким образом, вы можете позже распечатать сообщение и конкретную ошибку забросил функцией загрузки

+1

Это улавливает и затем * отбрасывает * объект исключения, поэтому нет, он не соответствует потребностей вопроса. В вопросе задается вопрос о том, как * сохранить * существующее исключение и разрешить тому же исключению, содержащему всю полезную информацию, продолжить распространение стека. – bignose