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)

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


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

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.


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'

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?


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

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

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)

3

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

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

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)


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)

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

-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.


como isso ajudaria neste caso?
— Johan Lundberg
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.