Como eu levanto a mesma exceção com uma mensagem personalizada em Python?

145

Eu tenho esse trybloco no meu código:

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

Estritamente falando, na verdade estou levantando outro ValueError , não o ValueErrorjogado por do_something...(), que é referido errneste caso. Como anexar uma mensagem personalizada err? Eu tento o código a seguir, mas falha devido a erruma ValueError instância não ser solicitada:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise err(errmsg)
Kit
fonte
13
@ Hamish, anexar informações adicionais e gerar novas exceções pode ser muito útil ao depurar.
Johan Lundberg
@ John Absolutamente - e é para isso que serve um stacktrace. Não consigo entender por que você editaria a mensagem de erro existente em vez de gerar um novo erro.
Hamish
@Hamish. Claro, mas você pode adicionar outras coisas. Para sua pergunta, dê uma olhada na minha resposta e no exemplo de UnicodeDecodeError. Se você tiver comentários sobre isso, talvez comente minha resposta.
Johan Lundberg
Possível duplicata de Adicionando informações a uma exceção?
Trevor Boyd Smith
1
@Kit é 2020 e python 3 está em toda parte. Por que não mudar a resposta aceita para a resposta de Ben :-)
mit

Respostas:

88

Atualização: para Python 3, verifique a resposta de Ben


Para anexar uma mensagem à exceção atual e aumentá-la novamente: (a tentativa / exceção externa é apenas para mostrar o efeito)

Para python 2.x, em que 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)

Isso também irá fazer a coisa certa , se erré derivado de ValueError. Por exemplo UnicodeDecodeError.

Observe que você pode adicionar o que quiser err. Por exemplo err.problematic_array=[1,2,3].


Edit: @Ducan aponta em um comentário acima não funciona com o python 3, pois .messagenão é membro ValueError. Em vez disso, você pode usar isso (python válido 2.6 ou posterior ou 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:

Dependendo da finalidade, você também pode optar por adicionar informações extras com o seu próprio nome de variável. Para python2 e 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
Johan Lundberg
fonte
9
Desde que você se esforçou para usar o tratamento de exceção no estilo Python 3 e print, provavelmente, deve observar que seu código não funciona no Python 3.x, pois não há messageatributo nas exceções. err.args = (err.args[0] + " hello",) + err.args[1:]pode funcionar de forma mais confiável (e depois converter para uma string para receber a mensagem).
Duncan
1
Infelizmente, não há garantia de que args [0] seja um tipo de string que representa uma mensagem de erro - "A tupla de argumentos fornecidos ao construtor de exceções. Algumas exceções internas (como IOError) esperam um certo número de argumentos e atribuem um significado especial a" os elementos dessa tupla, enquanto outros geralmente são chamados apenas com uma única cadeia de caracteres que fornece uma mensagem de erro. ". Portanto, o código não funcionará arg [0] não é uma mensagem de erro (pode ser um int ou uma string que representa um nome de arquivo).
Trent
1
@Taras, Interessante. Você tem alguma referência nisso? Então eu adicionaria a um membro completamente novo: err.my_own_extra_info. Ou encapsule tudo em minha própria exceção, mantendo as informações novas e as originais.
Johan Lundberg
2
Um exemplo real de quando args [0] não é uma mensagem de erro - docs.python.org/2/library/exceptions.html - "exception EnvironmentError A classe base para exceções que podem ocorrer fora do sistema Python: IOError, OSError. Quando exceções desse tipo são criadas com uma tupla de 2, o primeiro item está disponível no atributo errno da instância (é considerado um número de erro) e o segundo item está disponível no atributo strerror (geralmente é o associado mensagem de erro). A tupla também está disponível no atributo args. "
Trent
2
Eu não entendo nada disso. A única razão pela qual a configuração do .messageatributo faz alguma coisa aqui é que esse atributo é impresso explicitamente . Se você gerasse a exceção sem capturar e imprimir, o atributo não seria .messageútil.
precisa saber é o seguinte
171

Se você tiver sorte o suficiente para suportar apenas o python 3.x, isso realmente se tornará uma coisa de beleza :)

aumentar de

Podemos encadear as exceções usando raise de .

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

Nesse caso, a exceção que o chamador capturaria tem o número da linha do local onde aumentamos nossa exceção.

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

Observe que a exceção inferior possui apenas o rastreamento de pilha de onde criamos nossa exceção. O chamador ainda pode obter a exceção original acessando o __cause__atributo da exceção que eles capturam.

with_traceback

Ou você pode usar with_traceback .

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

Usando este formulário, a exceção que o chamador capturaria tem o retorno de onde ocorreu o erro original.

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

Observe que a exceção inferior tem a linha em que executamos a divisão inválida, bem como a linha em que repetimos a exceção.

Ben
fonte
1
É possível adicionar uma mensagem personalizada a uma exceção sem o rastreamento adicional? Por exemplo, pode raise Exception('Smelly socks') from eser modificado para adicionar apenas "meias fedorentas" como um comentário ao traceback original, em vez de introduzir um novo traceback.
Joelostblom
Esse é o comportamento que você terá de resposta de Johan Lundberg
Ben
3
isso é realmente adorável. Obrigado.
allanberry
3
Redigitar uma nova exceção ou criar exceções em cadeia com novas mensagens cria mais confusão do que o necessário em muitos casos. Por si só, as exceções são complexas de lidar. Uma estratégia melhor é apenas anexar sua mensagem ao argumento da exceção original, se possível, como em err.args + = ("message",) e aumentar novamente a mensagem de exceção. O rastreamento pode não levar você para os números de linha onde a exceção foi capturada, mas o levará para onde a exceção ocorreu com certeza.
user-asterix
2
Você também pode suprimir explicitamente a exibição da cadeia de exceções especificando None na cláusula 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

impressões:

There is a problem: invalid literal for int() with base 10: 'a'
eumiro
fonte
1
Fiquei me perguntando se havia um idioma Python para o que estou tentando fazer, além de levantar outra instância.
Kit
@Kit - Eu chamaria de 're-raise a exception': docs.python.org/reference/simple_stmts.html#raise
eumiro
1
@eumiro, não, você está fazendo uma nova exceção. Veja minha resposta. No seu link: "... mas aumentar com nenhuma expressão deve ser preferido se a exceção a ser gerada novamente foi a exceção ativa mais recente no escopo atual."
Johan Lundberg
3
@JohanLundberg - raisesem parâmetros está aumentando novamente. Se o OP quiser adicionar uma mensagem, ele precisará gerar uma nova exceção e poderá reutilizar a mensagem / tipo da exceção original.
eumiro
2
Se você deseja adicionar uma mensagem, não pode criar uma nova mensagem do zero, jogando "ValueError". Ao fazer isso, você destrói as informações subjacentes de que tipo de ValueError é (semelhante ao fatiamento em C ++). Ao repetir a mesma exceção com raise sem argumento, você passa o objeto original com esse tipo específico correto (derivado de ValueError).
Johan Lundberg 24/03
9

Parece que todas as respostas estão adicionando informações ao e.args [0], alterando a mensagem de erro existente. Existe uma desvantagem em estender a tupla de args? Eu acho que a possível vantagem é que você pode deixar a mensagem de erro original em paz nos casos em que a análise dessa string é necessária; e você pode adicionar vários elementos à tupla se o tratamento personalizado de erros produziu várias mensagens ou códigos de erro, nos casos em que o traceback seria analisado programaticamente (como por meio de uma ferramenta de monitoramento do sistema).

## 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')

ou

## 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')

Você pode ver uma desvantagem dessa abordagem?

Chris Johnson
fonte
Minha resposta mais antiga não altera e.args [0].
Johan Lundberg
4

Este modelo de código deve permitir que você crie uma exceção com uma mensagem personalizada.

try:
     raise ValueError
except ValueError as err:
    raise type(err)("my message")
Ruggero Turra
fonte
3
Isso não preserva o rastreamento de pilha.
plok
O questionador não especifica que o rastreamento da pilha seja preservado.
shrewmouse
4

Aumente a nova exceção com sua mensagem de erro usando

raise Exception('your error message')

ou

raise ValueError('your error message')

no local em que você deseja aumentá-lo OU anexar (substituir) a mensagem de erro na exceção atual usando 'from' (somente suporte ao Python 3.x):

except ValueError as e:
  raise ValueError('your message') from e
Alexey Antonenko
fonte
Thanx, @gberger, a abordagem de e 'realmente não é suportado pelo 2.x python
Alexey Antonenko
3

Essa é a função que eu uso para modificar a mensagem de exceção no Python 2.7 e 3.x, preservando o retorno original. Isso requersix

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)
Bryce Guinta
fonte
3

As exceções internas do Python 3 têm o strerror campo:

except ValueError as err:
  err.strerror = "New error message"
  raise err
user3761308
fonte
Isso não parece funcionar. Você está sentindo falta de algo?
MasayoMusic 13/07/19
2

A resposta atual não funcionou bem para mim, se a exceção não for capturada novamente, a mensagem anexada não será exibida.

Porém, fazer o mesmo abaixo mantém o rastreamento e mostra a mensagem anexada, independentemente de a exceção ser capturada novamente ou não.

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

(Eu usei o Python 2.7, não tentei no Python 3)

Zitrax
fonte
1

Nenhuma das soluções acima fez exatamente o que eu queria, que foi adicionar algumas informações à primeira parte da mensagem de erro, ou seja, eu queria que meus usuários vissem minha mensagem personalizada primeiro.

Isso funcionou para mim:

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)
RobinL
fonte
0

Isso funciona apenas com o Python 3 . Você pode modificar os argumentos originais da exceção e adicionar seus próprios argumentos.

Uma exceção lembra os argumentos com os quais foi criada. Presumo que isso seja para que você possa modificar a exceção.

Na função reraise, anexamos os argumentos originais da exceção com os novos argumentos que queremos (como uma mensagem). Finalmente, aumentamos novamente a exceção, preservando o histórico de rastreamento.

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()

resultado

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')
shrewmouse
fonte
-3

se você deseja personalizar o tipo de erro, uma coisa simples que você pode fazer é definir uma classe de erro com base no ValueError.

igni
fonte
como isso ajudaria neste caso?
Johan Lundberg