Uso do Python "raise from"

196

Qual é a diferença entre raisee raise fromno Python?

try:
    raise ValueError
except Exception as e:
    raise IndexError

que produz

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError
IndexError

e

try:
    raise ValueError
except Exception as e:
    raise IndexError from e

que produz

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

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

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError from e
IndexError
darkfeline
fonte
9
Você leu PEP-3134 ?
jonrsharpe
4
Agora use raise IndexError from None, digamos.
Martijn Pieters
11
Heh. raise IndexError from Falsegera um TypeError, não um IndexError. Fez o meu dia.
Mad Physicist
Não tenho certeza se este é o lugar certo para mencioná-lo, mas para quem usa o Spyder: Toda essa construção não funciona lá. Esse é um problema há mais de 3 anos ( github.com/spyder-ide/spyder/issues/2943 ), mas eles parecem pensar que não há necessidade de exceções encadeadas.
Emil Bode

Respostas:

227

A diferença é que, quando você usa from, o __cause__atributo é definido e a mensagem informa que a exceção foi causada diretamente por . Se você omitir, fromentão não __cause__será definido, mas o __context__atributo também poderá ser definido, e o rastreamento mostrará o contexto como durante o tratamento de algo que aconteceu .

Definir o que __context__acontece se você usou raiseem um manipulador de exceções; se você usou em raisequalquer outro lugar, também não __context__está definido.

Se a __cause__estiver definido, um __suppress_context__ = Truesinalizador também será definido na exceção; Quando __suppress_context__está definido como True, o __context__é ignorado ao imprimir um retorno.

Ao passar de um manipulador de exceções no qual você não deseja mostrar o contexto (não deseje uma mensagem durante o tratamento de outra exceção ), use raise ... from Nonepara definir __suppress_context__como True.

Em outras palavras, o Python define um contexto para exceções, para que você possa examinar onde uma exceção foi gerada, permitindo ver se outra exceção foi substituída por ela. Você também pode adicionar uma causa a uma exceção, explicitando explicitamente o traceback sobre a outra exceção (use palavras diferentes) e o contexto é ignorado (mas ainda pode ser observado durante a depuração). Usar raise ... from Nonepermite suprimir o contexto que está sendo impresso.

Veja a raisedocumentação documentada :

A fromcláusula é usada para o encadeamento de exceções: se fornecida, a segunda expressão deve ser outra classe ou instância de exceção, que será anexada à exceção gerada como o __cause__atributo (que é gravável). Se a exceção levantada não for tratada, as duas exceções serão impressas:

>>> try:
...     print(1 / 0)
... except Exception as exc:
...     raise RuntimeError("Something bad happened") from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

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

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

Um mecanismo semelhante funciona implicitamente se uma exceção é gerada dentro de um manipulador ou finallycláusula de exceção: a exceção anterior é anexada como o novo __context__atributo da exceção :

>>> try:
...     print(1 / 0)
... except:
...     raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

Consulte também a documentação de exceções internas para obter detalhes sobre o contexto e causar informações anexadas a exceções.

Martijn Pieters
fonte
11
Existe alguma razão para explicitamente encadear as exceções usando frome __cause__no lugar do implícito __context__? Existem casos em que alguém anexaria uma exceção diferente daquela detectada pelo except?
22814 darkfeline
13
@darkfeline: digamos que sua API de banco de dados suporta a abertura de bancos de dados de várias fontes, incluindo a web e o disco. Sua API sempre aumentará um DatabaseErrorse a abertura do banco de dados falhar. Mas se a falha for o resultado de um IOErrorporque um arquivo falhou ao abrir ou um HTTPErrorURL não funcionou, esse é o contexto que você deseja incluir explicitamente, para que o desenvolvedor que usa a API possa depurar o motivo. Nesse momento você usa raise DatabaseError from original_exception.
Martijn Pieters
4
@darkfeline: se esse desenvolvedor está envolvendo o uso da API do banco de dados em sua própria API e desejava repassar isso IOErrorou HTTPErrorpara seus consumidores, eles teriam que usar raise NewException from databaseexception.__cause__, agora usando uma exceção diferente DatabaseExceptiondaquela que acabaram de capturar.
Martijn Pieters
2
@ dan3: não, não há. O encadeamento de exceção é puramente um recurso do Python 3.
Martijn Pieters
5
@ laike9m: você quer dizer quando está lidando com uma exceção fooe quer criar uma nova exceção bar? Então você pode usar raise bar from fooe ter o estado Python que foo causou diretamentebar . Se você não usar from foo, o Python ainda imprimirá os dois, mas declare que durante o manuseio foo, barfoi gerada uma mensagem diferente, destinada a sinalizar um possível bug no manuseio de erros.
Martijn Pieters