Dodajesz informacje do wyjątku?


142

Chcę osiągnąć coś takiego:

def foo():
   try:
       raise IOError('Stuff ')
   except:
       raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
       e.message = e.message + 'happens at %s' % arg1
       raise

bar('arg1')
Traceback...
  IOError('Stuff Happens at arg1')

Ale otrzymuję:

Traceback..
  IOError('Stuff')

Jakieś wskazówki, jak to osiągnąć? Jak to zrobić zarówno w Pythonie 2, jak i 3?


Szukając dokumentacji dla messageatrybutu Exception , znalazłem to pytanie SO, BaseException.message jest przestarzałe w Pythonie 2.6 , co wydaje się wskazywać, że jego użycie jest teraz odradzane (i dlaczego nie ma go w dokumentacji).
martineau

niestety ten link wydaje się już nie działać.
Michael Scott Cuthbert

1
@MichaelScottCuthbert tutaj jest dobra alternatywa: itmaybeahack.com/book/python-2.6/html/p02/…
Niels Keurentjes

Oto naprawdę dobre wyjaśnienie statusu atrybutu wiadomości i jego związku z atrybutem args i PEP 352 . Pochodzi z bezpłatnej książki Building Skills in Python autorstwa Stevena F. Lotta.
martineau

Odpowiedzi:


118

Zrobiłbym to w ten sposób, aby zmiana jego typu foo()nie wymagała również zmiany go bar().

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
        foo()
    except Exception as e:
        raise type(e)(e.message + ' happens at %s' % arg1)

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1

Zaktualizuj 1

Oto niewielka modyfikacja, która zachowuje oryginalne dane śledzenia:

...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e), type(e)(e.message +
                               ' happens at %s' % arg1), sys.exc_info()[2]

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    foo()
  File "test.py", line 5, in foo
    raise IOError('Stuff')
IOError: Stuff happens at arg1

Zaktualizuj 2

W przypadku Pythona 3.x kod w mojej pierwszej aktualizacji jest niepoprawny składniowo, a pomysł posiadania messageatrybutu BaseExceptionzostał wycofany w zmianie na PEP 352 16.05.2012 (moja pierwsza aktualizacja została opublikowana 12.03.2012) . Tak więc obecnie, w Pythonie 3.5.2 i tak, musiałbyś zrobić coś wzdłuż tych linii, aby zachować śledzenie, a nie zakodować na stałe typ wyjątku w funkcji bar(). Zauważ również, że będzie linia:

During handling of the above exception, another exception occurred:

w wyświetlanych komunikatach śledzenia.

# for Python 3.x
...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e)(str(e) +
                      ' happens at %s' % arg1).with_traceback(sys.exc_info()[2])

bar('arg1')

Zaktualizuj 3

Komentator zapytał, czy istnieje sposób, który działałby zarówno w Pythonie 2, jak i 3. Chociaż odpowiedź może wydawać się „Nie” ze względu na różnice składniowe, można to obejść, używając funkcji pomocniczej, takiej jak reraise()w sixadd- na module. Jeśli więc z jakiegoś powodu wolisz nie korzystać z biblioteki, poniżej znajduje się uproszczona wersja samodzielna.

Zauważ również, że ponieważ wyjątek jest ponownie wywoływany w reraise()funkcji, pojawi się on w każdym wywołanym śledzeniu, ale ostateczny wynik jest taki, jaki chcesz.

import sys

if sys.version_info.major < 3:  # Python 2?
    # Using exec avoids a SyntaxError in Python 3.
    exec("""def reraise(exc_type, exc_value, exc_traceback=None):
                raise exc_type, exc_value, exc_traceback""")
else:
    def reraise(exc_type, exc_value, exc_traceback=None):
        if exc_value is None:
            exc_value = exc_type()
        if exc_value.__traceback__ is not exc_traceback:
            raise exc_value.with_traceback(exc_traceback)
        raise exc_value

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
        reraise(type(e), type(e)(str(e) +
                                 ' happens at %s' % arg1), sys.exc_info()[2])

bar('arg1')

3
To powoduje utratę śladu wstecznego, w pewnym sensie pokonując punkt dodawania informacji do istniejącego wyjątku. Ponadto nie działa wyjątki z ctor, który przyjmuje> 1 argumentów (typ jest czymś, czego nie możesz kontrolować z miejsca, w którym złapałeś wyjątek).
Václav Slavík

1
@ Václav: Dość łatwo jest zapobiec utracie śladu wstecznego - jak pokazano w aktualizacji, którą dodałem. Chociaż nadal nie obsługuje to każdego możliwego wyjątku, działa w przypadkach podobnych do tego, co pokazano w pytaniu PO.
martineau

1
To nie jest do końca w porządku. Jeśli typ (e) zastępuje __str__, możesz otrzymać niepożądane wyniki. Zwróć też uwagę, że drugi argument jest przekazywany do konstruktora podanego przez pierwszy argument, co daje nieco bezsensowne działanie type(e)(type(e)(e.message). Po trzecie, e.message jest przestarzałe i zastępuje e.args [0].
bukzor,

1
więc nie ma przenośnego sposobu, który działałby zarówno w Pythonie 2, jak i 3?
Elias Dorneles

1
@martineau Jaki jest cel importowania wewnątrz bloku except? Czy ma to na celu zaoszczędzenie pamięci przez importowanie tylko wtedy, gdy jest to konieczne?
AllTradesJack,

115

Jeśli przyszedłeś tutaj w poszukiwaniu rozwiązania dla Pythona 3, podręcznik mówi:

Podczas zgłaszania nowego wyjątku (zamiast używania bare raisedo ponownego zgłoszenia wyjątku, który jest obecnie obsługiwany), kontekst niejawnego wyjątku może zostać uzupełniony o jawną przyczynę, używając from with raise:

raise new_exc from original_exc

Przykład:

try:
    return [permission() for permission in self.permission_classes]
except TypeError as e:
    raise TypeError("Make sure your view's 'permission_classes' are iterable. "
                    "If you use '()' to generate a set with a single element "
                    "make sure that there is a comma behind the one (element,).") from e

Który na końcu wygląda tak:

2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
    return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable 

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
    # Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If 
     you use parens () to generate a set with a single element make 
     sure that there is a (comma,) behind the one element.

Przekształcenie całkowicie nijakiego TypeErrorw przyjemną wiadomość ze wskazówkami dotyczącymi rozwiązania bez zepsucia oryginalnego wyjątku.


14
Jest to najlepsze rozwiązanie, ponieważ wynikowy wyjątek wskazuje z powrotem na pierwotną przyczynę, podaj więcej szczegółów.
JT

czy jest jakieś rozwiązanie, które możemy dodać, ale nadal nie zgłosić nowego wyjątku? Mam na myśli po prostu rozszerzenie komunikatu wystąpienia wyjątku.
edcSam

Tak ~~ to działa, ale wydaje mi się, że jest to coś, czego nie należy mi robić. Wiadomość jest przechowywana w e.args, ale jest to krotka, więc nie można jej zmienić. Więc najpierw skopiuj argsdo listy, następnie zmodyfikuj ją, a następnie skopiuj z powrotem jako krotkę:args = list(e.args) args[0] = 'bar' e.args = tuple(args)
Chris

27

Zakładając, że nie chcesz lub nie możesz modyfikować foo (), możesz to zrobić:

try:
    raise IOError('stuff')
except Exception as e:
    if len(e.args) >= 1:
        e.args = (e.args[0] + ' happens',) + e.args[1:]
    raise

Jest to rzeczywiście jedyne rozwiązanie, które rozwiązuje problem w Pythonie 3 bez brzydkiego i mylącego komunikatu „Podczas obsługi powyższego wyjątku wystąpił kolejny wyjątek”.

W przypadku, gdy linia ponownego przebicia powinna zostać dodana do śladu stosu, wystarczy napisanie raise ezamiast raiseznaku.


ale w tym przypadku, jeśli wyjątek zmieni się w foo, muszę również zmienić pasek, prawda.?
anijhaw

1
Jeśli złapiesz Exception (edytowany powyżej), możesz przechwycić dowolny wyjątek biblioteki standardowej (jak również te, które dziedziczą po Exception i wywołają Exception .__ init__).
Steve Howard

6
aby być bardziej kompletnym / współpracującym, e.args = ('mynewstr' + e.args[0],) + e.args[1:]
dołącz

1
@ nmz787 W rzeczywistości jest to najlepsze rozwiązanie dla Pythona 3. Jaki dokładnie jest twój błąd?
Christian

1
@Dubslow i martineau Włączyłem twoje sugestie do edycji.
Christian

9

Jak dotąd nie podobają mi się wszystkie podane odpowiedzi. Nadal są zbyt gadatliwi, imho. W kodzie i wyjściu komunikatu.

Wszystko, co chcę, to ślad stosu wskazujący na wyjątek źródłowy, bez wyjątków pomiędzy, więc nie ma tworzenia nowych wyjątków, po prostu ponowne podniesienie oryginału ze wszystkimi odpowiednimi stanami ramki stosu, które tam doprowadziły.

Steve Howard dał miłą odpowiedź, którą chcę rozszerzyć, nie, zredukować ... tylko do Pythona 3.

except Exception as e:
    e.args = ("Some failure state", *e.args)
    raise

Jedyną nowością jest rozwijanie / rozpakowywanie parametrów, dzięki czemu jest mały i wystarczająco łatwy w użyciu.

Spróbuj:

foo = None

try:
    try:
        state = "bar"
        foo.append(state)

    except Exception as e:
        e.args = ("Appending '"+state+"' failed", *e.args)
        raise

    print(foo[0]) # would raise too

except Exception as e:
    e.args = ("print(foo) failed: " + str(foo), *e.args)
    raise

To da ci:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    foo.append(state)
AttributeError: ('print(foo) failed: None', "Appending 'bar' failed", "'NoneType' object has no attribute 'append'")

Prostym, ładnym nadrukiem może być coś takiego

print("\n".join( "-"*i+" "+j for i,j in enumerate(e.args)))

5

Jedną z przydatnych metod, które zastosowałem, jest użycie atrybutu klasy jako miejsca przechowywania szczegółów, ponieważ atrybut klasy jest dostępny zarówno z obiektu klasy, jak i instancji klasy:

class CustomError(Exception):
    def __init__(self, details: Dict):
        self.details = details

Następnie w swoim kodzie:

raise CustomError({'data': 5})

A kiedy łapie błąd:

except CustomError as e:
    # Do whatever you want with the exception instance
    print(e.details)

Niezbyt przydatne, ponieważ OP żąda, aby szczegóły zostały wydrukowane jako część śladu stosu, gdy oryginalny wyjątek zostanie zgłoszony i nie zostanie przechwycony.
cowbert

Myślę, że rozwiązanie jest dobre. Ale opis nie jest prawdziwy. Atrybuty klas są kopiowane do instancji podczas ich tworzenia. Dlatego po zmodyfikowaniu atrybutu „szczegóły” instancji atrybut klasy nadal będzie miał wartość Brak. W każdym razie chcemy tutaj takiego zachowania.
Adam Wallner,

2

W przeciwieństwie do poprzednich odpowiedzi, to działa w obliczu wyjątków z naprawdę złymi __str__. To ma zmienić typ jednak, aby czynnik się nieprzydatny __str__wdrożeń.

Nadal chciałbym znaleźć dodatkowe ulepszenie, które nie modyfikuje typu.

from contextlib import contextmanager
@contextmanager
def helpful_info():
    try:
        yield
    except Exception as e:
        class CloneException(Exception): pass
        CloneException.__name__ = type(e).__name__
        CloneException.__module___ = type(e).__module__
        helpful_message = '%s\n\nhelpful info!' % e
        import sys
        raise CloneException, helpful_message, sys.exc_traceback


class BadException(Exception):
    def __str__(self):
        return 'wat.'

with helpful_info():
    raise BadException('fooooo')

Oryginalne dane śledzenia i typ (nazwa) są zachowywane.

Traceback (most recent call last):
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
  File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
    self.gen.throw(type, value, traceback)
  File "re_raise.py", line 5, in helpful_info
    yield
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
__main__.BadException: wat.

helpful info!

2

Podam fragment kodu, którego często używam, gdy chcę dodać dodatkowe informacje do wyjątku. Pracuję zarówno w Pythonie 2.7 i 3.6.

import sys
import traceback

try:
    a = 1
    b = 1j

    # The line below raises an exception because
    # we cannot compare int to complex.
    m = max(a, b)  

except Exception as ex:
    # I create my  informational message for debugging:
    msg = "a=%r, b=%r" % (a, b)

    # Gather the information from the original exception:
    exc_type, exc_value, exc_traceback = sys.exc_info()

    # Format the original exception for a nice printout:
    traceback_string = ''.join(traceback.format_exception(
        exc_type, exc_value, exc_traceback))

    # Re-raise a new exception of the same class as the original one, 
    # using my custom message and the original traceback:
    raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))

Powyższy kod daje następujący wynik:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
     14     raise type(ex)(
     15         "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16         (msg, traceback_string))

TypeError: a=1, b=1j

ORIGINAL TRACEBACK:

Traceback (most recent call last):
  File "<ipython-input-6-09b74752c60d>", line 7, in <module>
    m = max(a, b)  # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers


Wiem, że to trochę odbiega od przykładu podanego w pytaniu, niemniej jednak mam nadzieję, że ktoś uzna to za przydatne.


1

Możesz zdefiniować własny wyjątek, który dziedziczy po innym i utworzyć własny konstruktor, aby ustawić wartość.

Na przykład:

class MyError(Exception):
   def __init__(self, value):
     self.value = value
     Exception.__init__(self)

   def __str__(self):
     return repr(self.value)

2
Nie dotyczy potrzeby zmiany / dołączania czegoś do messageoryginalnego wyjątku (ale myślę, że można to naprawić).
martineau

-6

Może

except Exception as e:
    raise IOError(e.message + 'happens at %s'%arg1)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.