Em Python, exceções são eventos que ocorrem durante a execução de um programa e interrompem o fluxo normal do código. O tratamento de exceções permite que você lide com esses erros de forma controlada, evitando que o programa quebre inesperadamente. Nesta aula, vamos explorar os mecanismos que Python oferece para capturar, lançar e gerenciar exceções, além de boas práticas para escrever código robusto.

Dominar o tratamento de exceções é essencial para criar aplicações confiáveis e com boa experiência do usuário. Vamos começar com a estrutura fundamental: try/except.

try/except/else/finally

O bloco try é usado para envolver código que pode gerar uma exceção. Se uma exceção ocorrer, o bloco except correspondente é executado. Você pode especificar o tipo de exceção a ser capturada, ou usar um except genérico. O bloco else é executado se nenhuma exceção ocorrer no try, e o bloco finally é executado sempre, independentemente de exceções.

Exemplo básico:

try:
    numero = int(input("Digite um número: "))
    resultado = 10 / numero
except ValueError:
    print("Erro: você não digitou um número válido.")
except ZeroDivisionError:
    print("Erro: divisão por zero não é permitida.")
else:
    print(f"Resultado: {resultado}")
finally:
    print("Bloco finally sempre executado.")

O else é útil para código que deve ser executado apenas se não houver exceções, e o finally é ideal para ações de limpeza, como fechar arquivos ou liberar recursos.

Tipos de exceção

Python possui diversas exceções embutidas, organizadas em hierarquia. A classe base é BaseException, da qual derivam Exception (para a maioria das exceções) e outras como SystemExit, KeyboardInterrupt. É importante capturar exceções específicas em vez de usar um except genérico, para evitar mascarar erros inesperados.

Principais exceções:

  • ValueError: quando uma função recebe um argumento com tipo correto mas valor inadequado.
  • TypeError: quando uma operação recebe um tipo de dado incompatível.
  • IndexError: ao acessar um índice de lista fora do intervalo.
  • KeyError: ao acessar uma chave de dicionário inexistente.
  • FileNotFoundError: ao tentar abrir um arquivo que não existe.

Exemplo:

try:
    lista = [1, 2, 3]
    print(lista[5])
except IndexError:
    print("Índice fora do intervalo.")

Você também pode capturar múltiplas exceções em um único except usando tuplas:

try:
    # código que pode gerar ValueError ou TypeError
except (ValueError, TypeError) as e:
    print(f"Erro: {e}")

raise

A instrução raise permite lançar exceções intencionalmente. Você pode usar exceções embutidas ou criar suas próprias exceções personalizadas (herdando de Exception). Isso é útil para sinalizar condições excepcionais em seu código.

Exemplo:

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

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

Para criar uma exceção personalizada:

class SaldoInsuficienteError(Exception):
    pass

def sacar(saldo, valor):
    if valor > saldo:
        raise SaldoInsuficienteError("Saldo insuficiente para saque.")
    return saldo - valor

Boas práticas

Ao tratar exceções, siga estas recomendações:

  • Seja específico: capture exceções específicas em vez de genéricas (except Exception). Evite except: sem tipo, pois captura até KeyboardInterrupt.
  • Não silencie exceções: evite except vazios ou que apenas passam; pelo menos registre o erro.
  • Use finally para limpeza: feche arquivos, conexões de rede, etc., no finally.
  • Evite usar exceções para controle de fluxo: exceções são para situações excepcionais, não para lógica normal.
  • Documente as exceções: indique em docstrings quais exceções sua função pode lançar.

Exemplo de boa prática:

try:
    with open("arquivo.txt", "r") as f:
        conteudo = f.read()
except FileNotFoundError:
    print("Arquivo não encontrado.")
except PermissionError:
    print("Sem permissão para ler o arquivo.")
except Exception as e:
    print(f"Erro inesperado: {e}")
    raise  # relança a exceção para depuração

Referências

Exercícios

  1. Crie uma função que receba dois números e retorne a divisão do primeiro pelo segundo. Trate exceções para divisão por zero e para entradas não numéricas.

    ✓ Resposta:
    def divisao_segura(a, b):
        try:
            resultado = a / b
        except ZeroDivisionError:
            return "Erro: divisão por zero."
        except TypeError:
            return "Erro: entradas devem ser números."
        else:
            return resultado
    
    print(divisao_segura(10, 2))  # 5.0
    print(divisao_segura(10, 0))  # Erro: divisão por zero.
    print(divisao_segura(10, "a"))  # Erro: entradas devem ser números.
  2. Escreva um programa que peça ao usuário um número inteiro e exiba seu inverso (1/n). Capture exceções para entrada inválida e divisão por zero.

    ✓ Resposta:
    try:
        n = int(input("Digite um número inteiro: "))
        inverso = 1 / n
    except ValueError:
        print("Erro: você não digitou um número inteiro.")
    except ZeroDivisionError:
        print("Erro: não é possível calcular inverso de zero.")
    else:
        print(f"O inverso é {inverso}")
    finally:
        print("Fim do programa.")
  3. Crie uma exceção personalizada chamada IdadeInvalidaError. Escreva uma função que receba uma idade e lance essa exceção se a idade for menor que 0 ou maior que 150. Trate a exceção adequadamente.

    ✓ Resposta:
    class IdadeInvalidaError(Exception):
        pass
    
    def validar_idade(idade):
        if idade < 0 or idade > 150:
            raise IdadeInvalidaError(f"Idade {idade} inválida. Deve estar entre 0 e 150.")
        return idade
    
    try:
        idade = int(input("Digite sua idade: "))
        validar_idade(idade)
        print(f"Idade {idade} válida.")
    except ValueError:
        print("Erro: entrada não é um número.")
    except IdadeInvalidaError as e:
        print(e)
  4. Escreva um bloco try/except/finally que tente abrir um arquivo chamado "dados.txt" para leitura. Se o arquivo não existir, capture FileNotFoundError e crie o arquivo vazio. Em finally, feche o arquivo (use with para simplificar).

    ✓ Resposta:
    try:
        with open("dados.txt", "r") as arquivo:
            conteudo = arquivo.read()
            print("Arquivo lido com sucesso.")
    except FileNotFoundError:
        print("Arquivo não encontrado. Criando arquivo vazio.")
        with open("dados.txt", "w") as arquivo:
            pass
    finally:
        print("Operação concluída.")
  5. Implemente uma calculadora simples que pede dois números e uma operação (+, -, *, /). Use exceções para tratar entradas inválidas e divisão por zero. Inclua um bloco else para exibir o resultado e finally para agradecer.

    ✓ Resposta:
    def calculadora():
        try:
            a = float(input("Digite o primeiro número: "))
            b = float(input("Digite o segundo número: "))
            op = input("Digite a operação (+, -, *, /): ")
            if op == "+":
                resultado = a + b
            elif op == "-":
                resultado = a - b
            elif op == "*":
                resultado = a * b
            elif op == "/":
                resultado = a / b
            else:
                print("Operação inválida.")
                return
        except ValueError:
            print("Erro: entradas devem ser números.")
        except ZeroDivisionError:
            print("Erro: divisão por zero.")
        else:
            print(f"Resultado: {resultado}")
        finally:
            print("Obrigado por usar a calculadora!")
    
    calculadora()