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
- Exceções - Tipos de exceções em Python
- Context Manager - Outra forma de gerenciar recursos
- Python - A linguagem de programação