Nesta aula, vamos aprender como lidar com erros em Python de forma controlada, utilizando o sistema de exceções da linguagem. O tratamento adequado de exceções é essencial para construir programas que não quebrem inesperadamente e que forneçam mensagens úteis ao usuário ou ao desenvolvedor.

Veremos como usar os blocos try, except, else e finally, conheceremos os principais tipos de exceção, aprenderemos a levantar exceções com raise e discutiremos boas práticas para um código mais limpo e seguro.

try/except/else/finally

O bloco try permite testar um trecho de código quanto a erros. Se ocorrer uma exceção, o fluxo é desviado para o bloco except, que trata o erro. Opcionalmente, podemos incluir um bloco else, executado se nenhuma exceção for levantada, e um bloco finally, que sempre executa, independentemente de ocorrer erro ou não.

Exemplo básico:

try:
    resultado = 10 / 0
except ZeroDivisionError:
    print("Divisão por zero não é permitida.")
else:
    print(f"Resultado: {resultado}")
finally:
    print("Bloco finally sempre executa.")

Neste exemplo, a divisão por zero levanta ZeroDivisionError, o except captura e imprime a mensagem, o else não executa, e o finally executa ao final.

Tipos de exceção

Python possui diversas exceções embutidas, organizadas em uma hierarquia. As mais comuns incluem:

  • Exception – base para todas as exceções não-sistema.
  • ValueError – quando uma função recebe um argumento com tipo correto mas valor inadequado.
  • TypeError – quando uma operação ou função recebe um argumento de tipo inadequado.
  • IndexError – ao acessar um índice inválido em uma sequência.
  • KeyError – ao acessar uma chave inexistente em um dicionário.
  • FileNotFoundError – ao tentar abrir um arquivo que não existe.

Podemos capturar exceções específicas ou usar uma cláusula except genérica (não recomendado). Exemplo:

try:
    numero = int(input("Digite um número: "))
except ValueError:
    print("Valor inválido!")
except KeyboardInterrupt:
    print("\nOperação cancelada.")

raise

O comando raise permite levantar uma exceção intencionalmente. Podemos usar uma exceção embutida ou criar uma personalizada. Exemplo:

def dividir(a, b):
    if b == 0:
        raise ValueError("Divisor não pode ser zero.")
    return a / b

try:
    print(dividir(10, 0))
except ValueError as e:
    print(e)

Também é possível re-lançar uma exceção capturada com raise vazio, preservando a pilha de chamadas.

Boas práticas

Algumas recomendações para tratamento de exceções:

  • Seja específico: capture exceções específicas, não use except Exception ou except: a menos que seja realmente necessário.
  • Evite silenciar exceções: não use except: pass sem registrar o erro de alguma forma.
  • Use finally para limpeza: feche arquivos, conexões ou libere recursos no finally.
  • Não abuse de exceções para controle de fluxo: prefira estruturas condicionais quando possível.
  • Documente exceções: em funções, indique quais exceções podem ser levantadas.

Exercícios

  1. Crie uma função que recebe dois números e retorna a divisão. Trate a exceção de divisão por zero e imprima uma mensagem amigável.

    ✓ Resposta:
    def dividir(a, b):
        try:
            return a / b
        except ZeroDivisionError:
            print("Erro: divisão por zero.")
            return None
  2. Escreva um código que tenta converter uma string para inteiro. Se falhar, capture ValueError e exiba uma mensagem. Use else para imprimir o número convertido e finally para imprimir "Fim da conversão".

    ✓ Resposta:
    try:
        valor = int(input("Digite um número: "))
    except ValueError:
        print("Valor inválido!")
    else:
        print(f"Número convertido: {valor}")
    finally:
        print("Fim da conversão.")
  3. Crie uma função que levanta uma exceção personalizada chamada SaldoInsuficienteError (herdando de Exception) quando o saldo for menor que o saque. Em seguida, trate essa exceção.

    ✓ Resposta:
    class SaldoInsuficienteError(Exception):
        pass
    
    def sacar(saldo, valor):
        if valor > saldo:
            raise SaldoInsuficienteError("Saldo insuficiente para o saque.")
        return saldo - valor
    
    try:
        novo_saldo = sacar(100, 150)
    except SaldoInsuficienteError as e:
        print(e)
  4. Escreva um programa que tenta abrir um arquivo chamado "dados.txt" para leitura. Se o arquivo não existir, capture FileNotFoundError e crie o arquivo. Use finally para fechar o arquivo se ele foi aberto.

    ✓ Resposta:
    arquivo = None
    try:
        arquivo = open("dados.txt", "r")
        conteudo = arquivo.read()
        print(conteudo)
    except FileNotFoundError:
        print("Arquivo não encontrado. Criando...")
        with open("dados.txt", "w") as f:
            f.write("Arquivo criado.")
    finally:
        if arquivo:
            arquivo.close()
  5. Implemente uma calculadora que lê dois números e uma operação (+, -, *, /). Trate exceções como ValueError (entrada inválida) e ZeroDivisionError. Use else para exibir o resultado e finally para imprimir "Operação finalizada".

    ✓ Resposta:
    try:
        a = float(input("Primeiro número: "))
        b = float(input("Segundo número: "))
        op = input("Operação (+, -, *, /): ")
        if op == '+':
            resultado = a + b
        elif op == '-':
            resultado = a - b
        elif op == '*':
            resultado = a * b
        elif op == '/':
            resultado = a / b
        else:
            raise ValueError("Operação inválida.")
    except ValueError as e:
        print(f"Erro: {e}")
    except ZeroDivisionError:
        print("Erro: divisão por zero.")
    else:
        print(f"Resultado: {resultado}")
    finally:
        print("Operação finalizada.")

Referências