Erros e Excessões

Prévio Próximo

Python Logo8. Erros e Exceções

Até agora, as mensagens de erro não ter sido mais do que o indicado, mas se você já tentou os exemplos você provavelmente já viu algum. Há (pelo menos) dois tipos distintos de erros: erros de sintaxe e exceções.

8.1. Erros de Sintaxe

Os erros de sintaxe, também conhecidos como erros de análise, são, talvez, o tipo mais comum de notificação que você recebe quando você ainda está aprendendo Python:
>>> while True print 'Hello world'
  File "<stdin>", line 1, in ?
    while True print 'Hello world'
                   ^
SyntaxError: invalid syntax
O parser repete a linha ofensiva e exibe uma pequena “seta” apontando para o primeiro ponto na linha onde o erro foi detectado. O erro é causado pelo (ou, pelo menos, detectado em) sinal que precede a flecha: no exemplo, o erro é detectado na palavra de impressão, uma vez que os dois pontos (‘:’) está faltando antes. O nome do arquivo e o número da linha são impressos para que você saiba onde procurar no caso da entradae ter vindo de um script.

8.2. Exceções

Mesmo que um comando ou expressão esteja sintaticamente correto, ele pode causar um erro quando é feita uma tentativa de executá-lo. Os erros detectados durante a execução são chamados de exceções e não são incondicionalmente fatal: em breve você vai aprender como lidar com eles em programas Python. A maioria das exceções entretanto não são manipuladas por programas, e resultam em mensagens de erro como mostrado aqui:
>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: cannot concatenate 'str' and 'int' objects
A última linha da mensagem de erro indica o que aconteceu. Exceções vêm em diferentes tipos, e o tipo é impresso como parte da mensagem: os tipos no exemplo são ZeroDivisionErrorNameError e TypeError. A string impressa como o tipo de excepção é o nome de exceção embutido que ocorreu. Isto serve para todas as exceções internas, mas não precisa ser verdadeiro para exceções definidas pelo usuário (embora seja uma convenção útil). Nomes de exceção padrão são identificadores embutidos (palavras-chave não-reservadas).
O resto da linha fornece detalhes com base no tipo de exceção e o que causou isso.

A parte anterior da mensagem de erro mostra o contexto onde a exceção aconteceu, sob a forma de um “stack traceback“. Em geral, contém uma parte do stack listando as linhas de código; no entanto, ela não irá exibir linhas lidas da entrada padrão.

Exceções internas listam as exceções e seus significados.

8.3. Manuseando Exceções

É possível escrever programas que tratam de exceções selecionadas. Veja o exemplo a seguir, que pede ao usuário para a entrada até que um inteiro válido seja inserido, mas permite que o usuário interrompa o programa (usando Control-C ou qualquer comando que seja o sistema operacional suportar); note que uma interrupção gerada pelo usuário será sinalizada através do levantamendo da exceção KeyboardInterrupt.
>>> while True:
...     try:
...         x = int(raw_input("Please enter a number: "))
...         break
...     except ValueError:
...         print "Oops!  That was no valid number.  Try again..."
...
A instrução try funciona da seguinte maneira.

  • Em primeiro lugar, a cláusula try (a(s) declaração(ões) entre as palavras-chave try e, except ) é(são) executada(s).
  • Se não ocorrer nenhuma exceção, a cláusula except é ignorada e a execução da instrução try será finalizada.
  • Se ocorrer uma exceção durante a execução da cláusula try, o resto da cláusula é ignorada. Então, se o seu tipo corresponde à exceção nomeado após a palavra-chave, except, a cláusula except será executada, e, em seguida, a execução continua após a instrução try.
  • Se ocorrer uma exceção que não coincide com a exceção nomeado na cláusula except, ela é repassada para  instruções exteriores ao try; se nenhum manipulador for encontrado, é uma exceção não tratada e execução pára com uma mensagem, como mostrado acima.
A instrução try pode ter mais de uma cláusula except, para especificar manipuladores para diferentes exceções. No máximo um manipulador será executado. Manipuladores de apenas lidam com exceções que ocorrem na cláusula try correspondente, não em outros manipuladores do mesmo try. Uma cláusula except pode nomear várias exceções como uma tuple entre parênteses, por exemplo:
... except (RuntimeError, TypeError, NameError):
...     pass
Note-se que os parênteses em torno desta tuple são necessários, porque exceto ValueError, e: era a sintaxe utilizada para o que é normalmente escrito como except RuntimeError as e: em Python moderna (descrito abaixo). A sintaxe antiga ainda é suportada para compatibilidade com versões anteriores. Isto significa except RuntimeError, TypeError não é equivalente a except (RuntimeError, TypeError): mas a except RuntimeError as TypeError: o que não é o que você quer.

A última cláusula except pode omitir o nome da(s) exceção(ões), para servir como um curinga. Utilize isto com extrema cautela, uma vez que é fácil de mascarar um erro de programação real deste geito! Também pode ser usada para imprimir uma mensagem de erro e, em seguida, re-lançar a exceção (permitindo um “caller” para lidar com a excepção tambem):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip()
except IOError as e:
    print "I/O error({0}): {1}".format(e.errno, e.strerror)
except ValueError:
    print "Could not convert data to an integer."
except:
    print "Unexpected error:", sys.exc_info()[0]
    raise
try … except possui uma cláusula else opcional, que, quando presente, deve seguir todas as outras cláusulas. É útil para o código que deve ser executado se a cláusula try não gerar uma exceção. Por exemplo:
for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'cannot open', arg
    else:
        print arg, 'has', len(f.readlines()), 'lines'
        f.close()
A utilização da cláusula else é melhor do que adicionar código adicional para a cláusula try porque evita acidentalmente capturar uma exceção que não foi suscitada pelo código que está sendo protegido pelo bloco try … except.
Quando ocorre uma exceção, ela pode ter um valor associado, também conhecido como o argumento de exceção. A presença e o tipo do argumento dependem do tipo de excepção.
A cláusula except pode especificar uma variável após o nome da exceção (ou tuple). A variável é ligada a uma instância de exceção com os argumentos armazenados em instance.args. Por conveniência, a instância de exceção define __str __() para que os argumentos possam ser impressos diretamente, sem ter que fazer referência a .args.
Pode-se também instanciar uma exceção antes de elevá-la e adicionar os atributos a ela como desejado.
>>> try:
...    raise Exception('spam', 'eggs')
... except Exception as inst:
...    print type(inst)     # the exception instance
...    print inst.args      # arguments stored in .args
...    print inst           # __str__ allows args to be printed directly
...    x, y = inst.args
...    print 'x =', x
...    print 'y =', y
...
<type 'exceptions.Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
Se uma exceção tiver um argumento, ele é impresso como a última parte (‘detalhe’) da mensagem para exceções não tratadas.

Manipuladores de exceção não apenas lidam com exceções, se ocorrer imediatamente na cláusula try, mas também se ocorrerem dentro das funções que são chamados (mesmo que indiretamente) na cláusula try. Por exemplo:

>>> def this_fails():
...     x = 1/0
...
>>> try:
...     this_fails()
... except ZeroDivisionError as detail:
...     print 'Handling run-time error:', detail
...
Handling run-time error: integer division or modulo by zero

8.4. Levantando Exceções

O comando raise permite ao programador forçar uma exceção específica a ocorrer. Por exemplo:
>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: HiThere
O único argumento para raise indica a exceção a ser levantada. Este deve ser um exemplo de exceção ou uma classe de exceção (a classe que deriva de Exception).

Se você precisa determinar se uma exceção foi levantada, mas não pretende lidar com ela, uma forma mais simples de o comando raise permite que você re-raise a exceção:

>>> try:
...     raise NameError('HiThere')
... except NameError:
...     print 'An exception flew by!'
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

8.5. Exceções Definidas pelo Usuário

Os programas podem nomear suas próprias exceções, criando uma nova classe de exceção (veja Classes para saber mais sobre classes Python). Exceções deve tipicamente ser derivadas da classe Exception, quer diretamente ou indiretamente. Por exemplo:
>>> class MyError(Exception):
...     def __init__(self, value):
...         self.value = value
...     def __str__(self):
...         return repr(self.value)
...
>>> try:
...     raise MyError(2*2)
... except MyError as e:
...     print 'My exception occurred, value:', e.value
...
My exception occurred, value: 4
>>> raise MyError('oops!')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'
Neste exemplo, o default __init__() de Exception foi substituído. O novo comportamento simplesmente cria o atributo  value. Isso substitui o comportamento padrão de criar o atributo args.
Classes de exceção podem ser definidas para fazer qualquer coisa que qualquer outra classe pode fazer, mas são geralmente mantidas simples, muitas vezes, apenas oferecendo uma série de atributos que permitem que informações sobre o erro sejam extraídas por manipuladores para a exceção. Ao criar um módulo que pode elevar vários erros distintos, uma prática comum é a criação de uma classe base para exceções definidas por esse módulo, e subclasse que crie classes de exceção específicas para diferentes condições de erro:
class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expr -- input expression in which the error occurred
        msg  -- explanation of the error    """

    def __init__(self, expr, msg):
        self.expr = expr
        self.msg = msg

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not    allowed.

    Attributes:
        prev -- state at beginning of transition
        next -- attempted new state
        msg  -- explanation of why the specific transition is not allowed    """

    def __init__(self, prev, next, msg):
        self.prev = prev
        self.next = next
        self.msg = msg
A maioria das exceções são definidas com nomes que terminam em “Error”, semelhante ao nome das exceções padrão.

Muitos módulos padrão definem suas próprias exceções para relatar erros que podem ocorrer em funções que eles definem. Mais informações sobre as classes é apresentada no capítulo Classes.

8.6. Definindo Ações de Limpeza (Clean-Up)

A instrução try possui outra cláusula opcional, que se destina a definir ações de limpeza que devem ser executadas em todas as circunstâncias. Por exemplo:
>>> try:
...     raise KeyboardInterrupt
... finally:
...     print 'Goodbye, world!'
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
A cláusula finally é sempre executada antes de deixar o try, se uma exceção ocorreu ou não. Quando uma exceção ocorreu na cláusula try e não foi tratado por uma cláusula except (ou que tenha ocorrido em uma cláusula except ou else), é re-levantada após a cláusula finally ser executada. A cláusula, finally, também é executada “no caminho de saída” quando qualquer outra cláusula do try é deixada via um comando breakcontinue ou return. Um exemplo mais complicado (tendo cláusulas except e finally no mesmo bloco try funciona como no Python 2.5):
>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print "division by zero!"
...     else:
...         print "result is", result
...     finally:
...         print "executing finally clause"
...
>>> divide(2, 1)
result is 2
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
Como você pode ver, a cláusula finally é executada em qualquer caso. A TypeError levantado por dividir duas strings não é tratada pela cláusula except e, portanto, é re-levantada após a cláusula finally ser executada.
Em aplicações do mundo real, a cláusula finally é útil para liberar recursos externos (como arquivos ou conexões de rede), independentemente se o uso do recurso foi bem sucedido.

8.7. Ações de Limpeza Pre-definidas

Alguns objetos definem ações de limpeza padrão a serem realizadas quando o objeto não é mais necessário, independentemente de a operação usando o objeto haver ou não tido de êxito ou falha. Veja o exemplo a seguir, que tenta abrir um arquivo e imprimir o seu conteúdo para a tela.
for line in open("myfile.txt"):
    print line,
O problema com esse código é que ele deixa o arquivo aberto por um período de tempo indeterminado após o código termine a execução. Isto não é um problema em scripts simples, mas pode ser um problema para aplicações maiores. A declaração with permite que objetos como arquivos a serem utilizados de uma forma que garante que eles estão sempre limpos prontamente e corretamente.
with open("myfile.txt") as f:
    for line in f:
        print line,
Após a instrução ser executada, o arquivo f está sempre fechado, mesmo que um problema seja encontrado ao processar as linhas. Outros objetos que fornecem ações de limpeza pré-definidas irá indicar isso em sua documentação.
 
Prévio Próximo