Pular para o conteúdo

    Capítulo 23, Básico

    Exceções

    Erros vão acontecer: arquivo ausente, rede fora, entrada inválida. O que define um bom programa é o que ele faz quando acontecem.

    Código deste capítulo: basico/cap23_excecoes.py

    Exceções são objetos organizados em hierarquia

    Um erro em tempo de execução é representado por uma exceção, que é uma instância de uma classe. Todas descendem de BaseException, e as que você trata no dia a dia descendem de Exception:

    basico/cap23_excecoes.pylinha 10
    print([c.__name__ for c in ZeroDivisionError.__mro__])
    
    Saída
    ['ZeroDivisionError', 'ArithmeticError', 'Exception', 'BaseException', 'object']
    

    Isso importa porque except ArithmeticError captura ZeroDivisionError também: capturar uma classe captura as filhas. Até os erros de sintaxe fazem parte da hierarquia (SyntaxError descende de Exception), embora apareçam na compilação, antes de o programa rodar.

    try, except, else e finally

    basico/cap23_excecoes.pylinhas 15 a 29
    def converter(texto):
        try:
            valor = int(texto)
        except ValueError:
            print(f"'{texto}' não é um inteiro")
            return None
        else:
            print("conversão feita")
            return valor
        finally:
            print("fim da tentativa")
    
    
    print(converter("42"))
    print(converter("abc"))
    
    Saída
    conversão feita
    fim da tentativa
    42
    'abc' não é um inteiro
    fim da tentativa
    None
    
    • try: o código que pode falhar.
    • except: o que fazer se aquela exceção ocorrer.
    • else: roda só se não houve exceção.
    • finally: roda sempre, mesmo com return ou erro. É o lugar da limpeza.

    Capture apenas o que você sabe tratar

    Eu sigo três regras. Primeiro: capture exceções específicas, nunca um except: vazio, que engole até o Ctrl+C. Segundo: o bloco try deve ser o menor possível. Terceiro: quando você precisar traduzir uma exceção, use raise ... from, que preserva a causa original:

    basico/cap23_excecoes.pylinhas 34 a 47
    def dividir(a, b):
        try:
            return a / b
        except ZeroDivisionError:
            return float("inf")
        except TypeError as erro:
            raise ValueError("os dois valores devem ser números") from erro
    
    
    print(dividir(1, 0))
    try:
        dividir(1, "a")
    except ValueError as erro:
        print(erro, "|", type(erro.__cause__).__name__)
    
    Saída
    inf
    os dois valores devem ser números | TypeError
    

    Exceções próprias

    Quando o seu domínio tem uma falha com nome (saldo insuficiente, pedido inexistente), crie uma classe. Quem usa seu código captura pelo nome, sem depender de texto de mensagem. As classes são o assunto do nível intermediário, então por enquanto copie o molde:

    basico/cap23_excecoes.pylinhas 52 a 68
    class SaldoInsuficiente(Exception):
        def __init__(self, saldo, valor):
            super().__init__(f"saldo {saldo} insuficiente para sacar {valor}")
            self.saldo = saldo
            self.valor = valor
    
    
    def sacar(saldo, valor):
        if valor > saldo:
            raise SaldoInsuficiente(saldo, valor)
        return saldo - valor
    
    
    try:
        sacar(100, 150)
    except SaldoInsuficiente as erro:
        print(erro, erro.valor - erro.saldo)
    
    Saída
    saldo 100 insuficiente para sacar 150 50
    

    Pedir perdão ou pedir licença

    Há dois estilos. LBYL (look before you leap) verifica antes de agir. EAFP (easier to ask forgiveness than permission) tenta e trata a falha. O estilo EAFP é o mais comum em Python, e é mais seguro em cenários concorrentes, porque entre "verificar" e "agir" o mundo pode mudar:

    basico/cap23_excecoes.pylinhas 73 a 85
    config = {"porta": 8080}
    
    if "host" in config:
        host = config["host"]
    else:
        host = "localhost"
    
    try:
        host = config["host"]
    except KeyError:
        host = "localhost"
    
    print(host)
    
    Saída
    localhost
    

    assert não é validação de entrada

    O assert serve para declarar o que precisa ser verdade no seu código, como uma checagem de sanidade para quem desenvolve. Ele pode ser desligado com a opção -O do interpretador. Para validar dados de fora (entrada de usuário, arquivos, rede), use if e raise.

    Exercício 1

    Divisão segura

    Escreva dividir_seguro(a, b) que devolva None quando b for zero.

    Exercício 2

    Validar uma idade

    Escreva ler_idade(texto) que devolva um inteiro e levante ValueError com mensagem clara se o texto não for número ou se o valor for negativo.