Voltar ao Glossario
Glossario Python

Try/Except em Python: O que É e Como Funciona | Python Brasil

Try/except em Python: EAFP vs LBYL, múltiplos excepts, contextlib.suppress, traceback, padrões de retry com tenacity e quando não usar try/except.

O que é Try/Except?

O bloco try/except é a estrutura do Python para tratamento de erros em tempo de execução. Ele permite “tentar” executar um bloco de código e, caso uma exceção seja lançada, capturá-la e lidar com ela de forma controlada, sem que o programa quebre inesperadamente.

Mais do que uma sintaxe de tratamento de erros, o try/except representa uma filosofia de programação característica do Python: preferir tentar fazer e lidar com falhas do que verificar condições antecipadamente.

Sintaxe completa

try:
    # Código que pode lançar uma exceção
    resultado = int(input("Digite um número: "))
    valor = 100 / resultado

except ZeroDivisionError:
    # Executado especificamente para divisão por zero
    print("Não é possível dividir por zero.")

except ValueError as e:
    # Captura a exceção e a expõe como 'e'
    print(f"Entrada inválida: {e}")

except (TypeError, AttributeError) as e:
    # Captura múltiplos tipos na mesma cláusula
    print(f"Erro de tipo ou atributo: {e}")

except Exception as e:
    # Captura qualquer exceção que herde de Exception
    # Use com parcimônia: prefira ser específico
    print(f"Erro inesperado: {e}")
    raise  # Re-lança para não silenciar erros desconhecidos

else:
    # Executado APENAS se nenhuma exceção foi lançada no bloco try
    print(f"Resultado: {valor}")

finally:
    # Executado SEMPRE: com sucesso, com erro, ou mesmo com return/break
    print("Bloco finally sempre executado.")

EAFP vs LBYL: a filosofia Python

O Python tem duas abordagens para lidar com situações que podem dar errado:

LBYL — Look Before You Leap (Olhe antes de pular): verificar condições antes de executar a operação. Estilo comum em C, Java e outras linguagens.

EAFP — Easier to Ask Forgiveness than Permission (Mais fácil pedir perdão que permissão): tentar executar e tratar o erro se ocorrer. Este é o estilo pythônico.

# LBYL: verificação antecipada
def obter_idade_lbyl(dados: dict, chave: str) -> int | None:
    if isinstance(dados, dict) and chave in dados:
        valor = dados[chave]
        if isinstance(valor, int) and valor > 0:
            return valor
    return None

# EAFP: tentativa e tratamento
def obter_idade_eafp(dados: dict, chave: str) -> int | None:
    try:
        valor = dados[chave]
        assert isinstance(valor, int) and valor > 0
        return valor
    except (KeyError, AssertionError, TypeError):
        return None

# LBYL tem um problema sutil: condição de corrida
# Em código concorrente, o estado pode mudar entre a verificação e o uso
import os

# LBYL (problemático em concorrência):
if os.path.exists("arquivo.txt"):
    # Outro processo pode deletar o arquivo aqui!
    with open("arquivo.txt") as f:
        conteudo = f.read()

# EAFP (seguro):
try:
    with open("arquivo.txt") as f:
        conteudo = f.read()
except FileNotFoundError:
    conteudo = ""

Ordenando múltiplas cláusulas except

A ordem das cláusulas except importa: o Python verifica cada uma de cima para baixo e executa a primeira que corresponder. Coloque exceções mais específicas antes das mais gerais:

# ERRADO: LookupError captura KeyError e IndexError antes delas
try:
    valor = dicionario[chave]
except LookupError:  # Captura KeyError e IndexError
    print("Não encontrado")
except KeyError:     # NUNCA será alcançado
    print("Chave inexistente")

# CORRETO: específico primeiro, geral depois
try:
    valor = dicionario[chave]
except KeyError:
    print(f"Chave '{chave}' não existe no dicionário")
except LookupError:
    print("Erro de busca genérico")
except Exception:
    print("Erro inesperado")
    raise

Performance: o custo do try/except

import timeit

# O bloco try tem custo MÍNIMO quando bem-sucedido
# A penalidade real é na CRIAÇÃO E PROPAGAÇÃO da exceção

# Medir custo do try sem exceção vs verificação prévia
codigo_try = """
try:
    x = d['chave']
except KeyError:
    x = None
"""

codigo_get = """
x = d.get('chave')
"""

d = {'chave': 42}

t1 = timeit.timeit(codigo_try, setup="d = {'chave': 42}", number=1_000_000)
t2 = timeit.timeit(codigo_get, setup="d = {'chave': 42}", number=1_000_000)
print(f"try/except (sucesso): {t1:.3f}s")
print(f"dict.get:             {t2:.3f}s")
# Ambos são muito próximos quando não há exceção

# Com exceção lançada, o custo aumenta significativamente
# Use LBYL em loops muito apertados com alta taxa de falha esperada

Context managers como alternativa

Muitas situações que exigiam try/finally manual podem ser expressas com gerenciadores de contexto, que são mais claros e menos propensos a erros:

# Sem context manager (verboso e propenso a erros)
arquivo = open("dados.txt")
try:
    conteudo = arquivo.read()
finally:
    arquivo.close()  # E se esquecermos? Vazamento de recurso.

# Com context manager (limpo e seguro)
with open("dados.txt") as arquivo:
    conteudo = arquivo.read()
# arquivo.close() é chamado automaticamente

# Context managers para locks, transações, conexões...
import threading

lock = threading.Lock()
with lock:  # Garantia de liberação mesmo se exceção for lançada
    recurso_compartilhado.modificar()

# Criando seu próprio context manager
from contextlib import contextmanager

@contextmanager
def transacao_banco(conexao):
    try:
        yield conexao
        conexao.commit()
    except Exception:
        conexao.rollback()
        raise

contextlib.suppress: silenciando exceções específicas

from contextlib import suppress
import os

# Sem suppress (verboso)
try:
    os.remove("arquivo_temporario.txt")
except FileNotFoundError:
    pass  # Tudo bem se não existir

# Com suppress (mais expressivo)
with suppress(FileNotFoundError):
    os.remove("arquivo_temporario.txt")

# Múltiplas exceções
with suppress(FileNotFoundError, PermissionError):
    os.remove("arquivo_protegido.txt")

Módulo traceback e sys.exc_info()

import traceback
import sys

def analisar_excecao():
    try:
        1 / 0
    except ZeroDivisionError:
        # sys.exc_info(): retorna (tipo, valor, traceback)
        tipo, valor, tb = sys.exc_info()
        print(f"Tipo: {tipo.__name__}")
        print(f"Valor: {valor}")

        # traceback.format_exc(): traceback como string
        texto_tb = traceback.format_exc()
        print(texto_tb)

        # traceback.print_exc(): imprime o traceback completo
        traceback.print_exc()

        # Extraindo informações específicas
        frames = traceback.extract_tb(tb)
        ultimo_frame = frames[-1]
        print(f"Arquivo: {ultimo_frame.filename}")
        print(f"Linha: {ultimo_frame.lineno}")
        print(f"Função: {ultimo_frame.name}")
        print(f"Código: {ultimo_frame.line}")

logging.exception: registrando erros com contexto

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def processar_pagamento(pedido_id: int, valor: float) -> bool:
    try:
        gateway = GatewayPagamento()
        resultado = gateway.cobrar(pedido_id, valor)
        logger.info("Pagamento aprovado: pedido=%d, valor=%.2f", pedido_id, valor)
        return True
    except GatewayTimeout:
        # logging.exception inclui o traceback completo
        logger.exception(
            "Timeout no gateway de pagamento: pedido=%d, valor=%.2f",
            pedido_id, valor
        )
        return False
    except PagamentoRecusado as e:
        logger.warning(
            "Pagamento recusado: pedido=%d, codigo=%s, motivo=%s",
            pedido_id, e.codigo, e.motivo
        )
        return False
    except Exception:
        logger.exception("Erro inesperado no processamento do pagamento")
        raise  # Re-lança exceções verdadeiramente inesperadas

Padrões de retry com tenacity

Tentar novamente após falhas transientes é um padrão comum em sistemas distribuídos:

# pip install tenacity
from tenacity import (
    retry,
    stop_after_attempt,
    wait_exponential,
    retry_if_exception_type,
    before_sleep_log
)
import logging
import requests

logger = logging.getLogger(__name__)

@retry(
    retry=retry_if_exception_type((ConnectionError, TimeoutError)),
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=1, max=10),
    before_sleep=before_sleep_log(logger, logging.WARNING)
)
def buscar_dados_api(url: str) -> dict:
    """Busca dados com retry automático em falhas de rede."""
    resposta = requests.get(url, timeout=5)
    resposta.raise_for_status()
    return resposta.json()

# Implementação manual simples (sem biblioteca)
import time

def com_retry(funcao, tentativas=3, espera=1.0, excecoes=(Exception,)):
    for tentativa in range(1, tentativas + 1):
        try:
            return funcao()
        except excecoes as e:
            if tentativa == tentativas:
                raise
            logger.warning("Tentativa %d/%d falhou: %s", tentativa, tentativas, e)
            time.sleep(espera * tentativa)  # Backoff linear

Quando NÃO usar try/except

Existem situações em que try/except não é a ferramenta adequada:

Não use para controle de fluxo normal: se uma condição é esperada e rotineira, use lógica condicional. dict.get(), list.index() com verificação prévia, ou operadores como or são mais claros.

Não silencie exceções sem motivo: except: pass esconde bugs. Sempre logue ou trate a exceção de forma significativa.

Não capture exceções mais amplas do que o necessário: except Exception em vez de except (ValueError, KeyError) dificulta a depuração.

Não use para validação de entrada do usuário em loops: valide antes com condicionais; o overhead de exceções em loops muito apertados é relevante.

# RUIM: usar exceção para lógica de negócio rotineira
def verificar_usuario_existe(user_id: int) -> bool:
    try:
        buscar_usuario(user_id)
        return True
    except UsuarioNaoEncontrado:
        return False

# BOM: a função de busca pode retornar None
def verificar_usuario_existe(user_id: int) -> bool:
    return buscar_usuario(user_id) is not None

Termos Relacionados