Jak zgłosić ten sam wyjątek za pomocą niestandardowego komunikatu w Pythonie?


145

Mam ten tryblok w swoim kodzie:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise ValueError(errmsg)

Ściśle mówiąc, w rzeczywistości wychowuję innego ValueError , a nie ValueErrorrzuconego przez do_something...(), o którym mowa errw tym przypadku. Jak dołączyć wiadomość niestandardową do err? Próbuję poniższy kod, ale nie z powodu err, na ValueError przykład , nie jest wymagalne:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise err(errmsg)

13
@ Hamish, dołączanie dodatkowych informacji i ponowne zgłaszanie wyjątków może być bardzo pomocne podczas debugowania.
Johan Lundberg

@Johan Absolutely - i do tego służy śledzenie stosu. Nie bardzo rozumiem, dlaczego chcesz edytować istniejący komunikat o błędzie zamiast zgłaszać nowy błąd.
Hamish

@Hamish. Jasne, ale możesz dodać inne rzeczy. Na swoje pytanie spójrz na moją odpowiedź i przykład UnicodeDecodeError. Jeśli masz uwagi na ten temat, może zamiast tego skomentuj moją odpowiedź.
Johan Lundberg


1
@Kit jest rok 2020, a Python 3 jest wszędzie. Dlaczego nie zmienić zaakceptowanej odpowiedzi na odpowiedź Bena :-)
mit

Odpowiedzi:


88

Aktualizacja: w przypadku Pythona 3 sprawdź odpowiedź Bena


Aby dołączyć wiadomość do bieżącego wyjątku i ponownie go zgłosić: (zewnętrzna try / z wyjątkiem służy tylko do pokazania efektu)

Dla pythona 2.x, gdzie x> = 6:

try:
    try:
      raise ValueError  # something bad...
    except ValueError as err:
      err.message=err.message+" hello"
      raise              # re-raise current exception
except ValueError as e:
    print(" got error of type "+ str(type(e))+" with message " +e.message)

Będzie to również zrobić dobry uczynek , jeśli errjest pochodząca z ValueError. Na przykład UnicodeDecodeError.

Pamiętaj, że możesz dodać wszystko, co chcesz err. Na przykład err.problematic_array=[1,2,3].


Edycja: @Ducan wskazuje w komentarzu, który powyżej nie działa z Pythonem 3, ponieważ .messagenie jest członkiem ValueError. Zamiast tego możesz użyć tego (poprawny Python 2.6 lub nowszy lub 3.x):

try:
    try:
      raise ValueError
    except ValueError as err:
       if not err.args: 
           err.args=('',)
       err.args = err.args + ("hello",)
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e.args))

Edit2:

W zależności od celu możesz również zdecydować się na dodanie dodatkowych informacji pod własną nazwą zmiennej. Zarówno dla python2, jak i python3:

try:
    try:
      raise ValueError
    except ValueError as err:
       err.extra_info = "hello"
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e))
    if 'extra_info' in dir(e):
       print e.extra_info

9
Ponieważ dołożyłeś wszelkich starań, aby użyć obsługi wyjątków w stylu Python 3 i printprawdopodobnie powinieneś zauważyć, że twój kod nie działa w Pythonie 3.x, ponieważ nie ma messageatrybutu dla wyjątków. err.args = (err.args[0] + " hello",) + err.args[1:]może działać bardziej niezawodnie (a następnie po prostu przekonwertować na łańcuch, aby otrzymać wiadomość).
Duncan

1
Niestety nie ma gwarancji, że args [0] jest typem łańcucha reprezentującym komunikat o błędzie - „Krotka argumentów przekazana konstruktorowi wyjątków. Niektóre wbudowane wyjątki (takie jak IOError) oczekują określonej liczby argumentów i przypisują specjalne znaczenie elementy tej krotki, podczas gdy inne są zwykle wywoływane tylko z pojedynczym ciągiem znaków, który wyświetla komunikat o błędzie. ". Więc kod nie będzie działał arg [0] nie jest komunikatem o błędzie (może to być int lub może to być ciąg reprezentujący nazwę pliku).
Trent

1
@Taras, ciekawe. Czy masz o tym odniesienie? Następnie dodałbym do zupełnie nowego członka: err.my_own_extra_info. Lub ująć to wszystko w moim własnym wyjątku, zachowując nowe i oryginalne informacje.
Johan Lundberg

2
Prawdziwy przykład sytuacji, gdy args [0] nie jest komunikatem o błędzie - docs.python.org/2/library/exceptions.html - „wyjątek EnvironmentError” Klasa bazowa wyjątków, które mogą wystąpić poza systemem Python: IOError, OSError. Gdy wyjątki tego typu są tworzone z dwiema krotkami, pierwsza pozycja jest dostępna w atrybucie errno instancji (przyjmuje się, że jest to numer błędu), a druga pozycja jest dostępna w atrybucie strerror (zwykle jest to skojarzony komunikat o błędzie). Sama krotka jest również dostępna w atrybucie args. "
Trent

2
W ogóle tego nie rozumiem. Jedynym powodem, dla którego ustawienie .messageatrybutu robi cokolwiek tutaj, jest to, że ten atrybut jest jawnie drukowany. Gdybyś miał zgłosić wyjątek bez przechwytywania i drukowania, nie zobaczyłbyś, że .messageatrybut robi cokolwiek użytecznego.
DanielSank

171

Jeśli masz szczęście, że obsługujesz tylko Pythona 3.x, to naprawdę staje się piękna :)

podnieść z

Możemy połączyć wyjątki za pomocą funkcji raise from .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks') from e

W tym przypadku wyjątek, który przechwyciłby Twój rozmówca, ma numer wiersza miejsca, w którym zgłosiliśmy wyjątek.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

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

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks') from e
Exception: Smelly socks

Zwróć uwagę, że dolny wyjątek ma tylko ślad stosu, z którego wywołaliśmy nasz wyjątek. Wzywający może nadal uzyskać oryginalny wyjątek, uzyskując dostęp do __cause__atrybutu wychwyconego wyjątku.

with_traceback

Lub możesz użyć with_traceback .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks').with_traceback(e.__traceback__)

Korzystając z tego formularza, wyjątek, który Twój wywołujący mógłby złapać, ma śledzenie, z którego wystąpił pierwotny błąd.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks').with_traceback(e.__traceback__)
  File "test.py", line 2, in <module>
    1 / 0
Exception: Smelly socks

Zwróć uwagę, że dolny wyjątek zawiera wiersz, w którym wykonaliśmy nieprawidłowy podział, a także wiersz, w którym ponownie podniesiono wyjątek.


1
Czy można dodać niestandardową wiadomość do wyjątku bez dodatkowego śledzenia? Na przykład, można raise Exception('Smelly socks') from ezmodyfikować, aby po prostu dodać „Śmierdzące skarpetki” jako komentarz do oryginalnego śledzenia zamiast wprowadzać nowy własny ślad.
joelostblom

Takie zachowanie otrzymasz od odpowiedzi Johana Lundberga
Ben

3
to jest naprawdę cudowne. Dziękuję Ci.
alanberry

3
Ponowne zgłoszenie nowego wyjątku lub łańcuchowe wywołanie wyjątków z nowymi wiadomościami powoduje w wielu przypadkach większe zamieszanie niż jest to potrzebne. Same wyjątki są skomplikowane w obsłudze. Lepszą strategią jest po prostu dołączenie wiadomości do argumentu oryginalnego wyjątku, jeśli to możliwe, jak w err.args + = ("komunikat",) i ponowne zgłoszenie komunikatu o wyjątku. Śledzenie może nie prowadzić do numerów linii, w których został przechwycony wyjątek, ale z pewnością przeniesie Cię do miejsca, w którym wystąpił wyjątek.
asterix użytkownika

2
Można również jawnie wyłączyć wyświetlanie łańcucha wyjątków, określając None w klauzuli from:raise RuntimeError("Something bad happened") from None
pfabri

10
try:
    try:
        int('a')
    except ValueError as e:
        raise ValueError('There is a problem: {0}'.format(e))
except ValueError as err:
    print err

wydruki:

There is a problem: invalid literal for int() with base 10: 'a'

1
Zastanawiałem się, czy istnieje idiom Pythona dla tego, co próbuję zrobić, poza podniesieniem innej instancji.
Kit

@Kit - nazwałbym to „ponownym zgłoszeniem wyjątku”: docs.python.org/reference/simple_stmts.html#raise
eumiro,

1
@eumiro, Nie, robisz nowy wyjątek. Zobacz moją odpowiedź. Z Twojego linku: „... ale podnieś bez wyrażeń powinno być preferowane, jeśli wyjątek do ponownego zgłoszenia był ostatnio aktywnym wyjątkiem w bieżącym zakresie”.
Johan Lundberg

3
@JohanLundberg - raisebez parametrów ponownie podnosi. Jeśli OP chce dodać wiadomość, musi zgłosić nowy wyjątek i może ponownie wykorzystać wiadomość / typ oryginalnego wyjątku.
eumiro

2
Jeśli chcesz dodać wiadomość, nie możesz utworzyć nowej wiadomości od zera, rzucając „ValueError”. W ten sposób niszczysz podstawowe informacje o tym, jakiego rodzaju ValueError jest (podobnie do wycinania w C ++). Przez ponowne rzuca się sam wyjątek z podbiciem bez argumentu, zdasz oryginalnego obiektu z tym zadaniu odpowiedniego typu (pochodzące z ValueError).
Johan Lundberg

9

Wygląda na to, że wszystkie odpowiedzi dodają informacje do e.args [0], zmieniając w ten sposób istniejący komunikat o błędzie. Czy zamiast tego rozszerzanie krotki argumentów ma jakąś wadę? Myślę, że możliwą zaletą jest to, że możesz zostawić oryginalny komunikat o błędzie w spokoju w przypadkach, gdy analiza tego ciągu jest potrzebna; i możesz dodać wiele elementów do krotki, jeśli niestandardowa obsługa błędów wygenerowała kilka komunikatów lub kodów błędów, w przypadkach, w których śledzenie zostanie przeanalizowane programowo (np. za pomocą narzędzia do monitorowania systemu).

## Approach #1, if the exception may not be derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args = (e.args if e.args else tuple()) + ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

lub

## Approach #2, if the exception is always derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args += ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

Czy widzisz wady tego podejścia?


Moja starsza odpowiedź nie zmienia e.args [0].
Johan Lundberg

4

Ten szablon kodu powinien umożliwić zgłoszenie wyjątku z niestandardową wiadomością.

try:
     raise ValueError
except ValueError as err:
    raise type(err)("my message")

3
Nie zachowuje to śladu stosu.
plok

Osoba pytająca nie określa, że ​​ślad stosu ma zostać zachowany.
shrewmouse

4

Podnieś nowy wyjątek za pomocą komunikatu o błędzie za pomocą

raise Exception('your error message')

lub

raise ValueError('your error message')

w miejscu, w którym chcesz go podnieść LUB dołącz (zamień) komunikat o błędzie do bieżącego wyjątku za pomocą 'from' (tylko obsługiwany Python 3.x):

except ValueError as e:
  raise ValueError('your message') from e

Thanx, @gberger, podejście „from e” w rzeczywistości nie jest obsługiwane przez Python 2.x
Alexey Antonenko,

3

Jest to funkcja, której używam do modyfikowania komunikatu o wyjątku w Pythonie 2.7 i 3.x przy jednoczesnym zachowaniu oryginalnego śledzenia. To wymagasix

def reraise_modify(caught_exc, append_msg, prepend=False):
    """Append message to exception while preserving attributes.

    Preserves exception class, and exception traceback.

    Note:
        This function needs to be called inside an except because
        `sys.exc_info()` requires the exception context.

    Args:
        caught_exc(Exception): The caught exception object
        append_msg(str): The message to append to the caught exception
        prepend(bool): If True prepend the message to args instead of appending

    Returns:
        None

    Side Effects:
        Re-raises the exception with the preserved data / trace but
        modified message
    """
    ExceptClass = type(caught_exc)
    # Keep old traceback
    traceback = sys.exc_info()[2]
    if not caught_exc.args:
        # If no args, create our own tuple
        arg_list = [append_msg]
    else:
        # Take the last arg
        # If it is a string
        # append your message.
        # Otherwise append it to the
        # arg list(Not as pretty)
        arg_list = list(caught_exc.args[:-1])
        last_arg = caught_exc.args[-1]
        if isinstance(last_arg, str):
            if prepend:
                arg_list.append(append_msg + last_arg)
            else:
                arg_list.append(last_arg + append_msg)
        else:
            arg_list += [last_arg, append_msg]
    caught_exc.args = tuple(arg_list)
    six.reraise(ExceptClass,
                caught_exc,
                traceback)

3

Wyjątki wbudowane w Pythonie 3 mają strerrorpole:

except ValueError as err:
  err.strerror = "New error message"
  raise err

To nie działa. Brakuje Ci czegoś?
MasayoMusic

2

Bieżąca odpowiedź nie działa dobrze, jeśli wyjątek nie zostanie ponownie przechwycony, dołączona wiadomość nie jest wyświetlana.

Ale postępowanie jak poniżej pozwala zachować ślad i pokazuje dołączoną wiadomość niezależnie od tego, czy wyjątek zostanie ponownie przechwycony, czy nie.

try:
  raise ValueError("Original message")
except ValueError as err:
  t, v, tb = sys.exc_info()
  raise t, ValueError(err.message + " Appended Info"), tb

(Użyłem Pythona 2.7, nie próbowałem tego w Pythonie 3)


1

Żadne z powyższych rozwiązań nie zrobiło dokładnie tego, co chciałem, czyli dodania pewnych informacji do pierwszej części komunikatu o błędzie, tj. Chciałem, aby użytkownicy najpierw zobaczyli mój niestandardowy komunikat.

To zadziałało dla mnie:

exception_raised = False
try:
    do_something_that_might_raise_an_exception()
except ValueError as e:
    message = str(e)
    exception_raised = True

if exception_raised:
    message_to_prepend = "Custom text"
    raise ValueError(message_to_prepend + message)

0

Działa to tylko z Pythonem 3 . Możesz zmodyfikować oryginalne argumenty wyjątku i dodać własne argumenty.

Wyjątek zapamiętuje argumenty, z którymi został utworzony. Zakładam, że jest to po to, abyś mógł zmodyfikować wyjątek.

W funkcji reraisepoprzedzamy oryginalne argumenty wyjątku dowolnymi nowymi argumentami, które chcemy (np. Wiadomość). Na koniec ponownie zgłaszamy wyjątek, zachowując historię śledzenia wstecz.

def reraise(e, *args):
  '''re-raise an exception with extra arguments
  :param e: The exception to reraise
  :param args: Extra args to add to the exception
  '''

  # e.args is a tuple of arguments that the exception with instantiated with.
  #
  e.args = args + e.args

  # Recreate the expection and preserve the traceback info so thta we can see 
  # where this exception originated.
  #
  raise e.with_traceback(e.__traceback__)   


def bad():
  raise ValueError('bad')

def very():
  try:
    bad()
  except Exception as e:
    reraise(e, 'very')

def very_very():
  try:
    very()
  except Exception as e:
    reraise(e, 'very')

very_very()

wynik

Traceback (most recent call last):
  File "main.py", line 35, in <module>
    very_very()
  File "main.py", line 30, in very_very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 28, in very_very
    very()
  File "main.py", line 24, in very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 22, in very
    bad()
  File "main.py", line 18, in bad
    raise ValueError('bad')
ValueError: ('very', 'very', 'bad')

-3

jeśli chcesz dostosować typ błędu, prostą rzeczą, jaką możesz zrobić, jest zdefiniowanie klasy błędu w oparciu o wartość ValueError.


jak to pomogłoby w tym przypadku?
Johan Lundberg
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.