Expressões regulares com o módulo re
1. Introdução ao módulo re e conceitos fundamentais
Expressões regulares (regex) são sequências de caracteres que definem padrões de busca em textos. Em Python, o módulo re fornece todas as ferramentas necessárias para trabalhar com regex de forma eficiente e intuitiva.
import re
# Exemplo básico: buscar um padrão em uma string
texto = "O telefone é 11-99999-8888"
padrao = r"\d{2}-\d{5}-\d{4}"
resultado = re.search(padrao, texto)
if resultado:
print(f"Telefone encontrado: {resultado.group()}")
Os metacaracteres fundamentais do módulo re incluem:
- . — qualquer caractere (exceto nova linha)
- ^ — início da string
- $ — final da string
- * — zero ou mais repetições
- + — uma ou mais repetições
- ? — zero ou uma repetição
- [] — conjunto de caracteres
- | — operador OU
- () — grupo de captura
# Exemplos de metacaracteres
texto_teste = "casa, carro, conta, curto"
padrao_c = r"c[aeiou]"
resultados = re.findall(padrao_c, texto_teste)
print(resultados) # ['ca', 'ca', 'co', 'cu']
2. Compilação de padrões e flags
A compilação de padrões com re.compile() oferece vantagens significativas de desempenho quando o mesmo padrão é usado repetidamente.
# Compilação simples
padrao_compilado = re.compile(r"\b\w{5}\b")
texto = "Python é uma linguagem poderosa e versátil"
palavras_5_letras = padrao_compilado.findall(texto)
print(palavras_5_letras) # ['Python', 'linguagem']
# Compilação com flags
padrao_email = re.compile(
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
re.IGNORECASE
)
texto_emails = "Contato: EMAIL@EXAMPLE.COM ou teste@exemplo.com"
emails = padrao_email.findall(texto_emails)
print(emails)
Flags importantes:
- re.IGNORECASE — ignora diferenças entre maiúsculas/minúsculas
- re.MULTILINE — ^ e $ correspondem a início/fim de linha
- re.DOTALL — . corresponde também a nova linha
- re.VERBOSE — permite espaços e comentários no padrão
# Exemplo com múltiplas flags
padrao_complexo = re.compile(r"""
^ # início da linha
\d{3} # código de área
[-.\s]? # separador opcional
\d{3} # três primeiros dígitos
[-.\s]? # separador opcional
\d{4} # quatro últimos dígitos
$ # final da linha
""", re.VERBOSE | re.MULTILINE)
telefones = """123-456-7890
987.654.3210
555 123 4567"""
print(padrao_complexo.findall(telefones))
3. Funções de busca e correspondência
Cada função de busca tem seu propósito específico:
texto_exemplo = "Python é uma linguagem Pythonica"
# re.match() - busca apenas no início da string
match_inicio = re.match(r"Python", texto_exemplo)
print(match_inicio.group() if match_inicio else "Não encontrado")
# re.search() - busca em toda a string
search_qualquer = re.search(r"Python", texto_exemplo)
print(search_qualquer.group() if search_qualquer else "Não encontrado")
# re.fullmatch() - correspondência exata da string inteira
texto_exato = "Python"
full_match = re.fullmatch(r"Python", texto_exato)
print(full_match.group() if full_match else "Não corresponde")
# re.findall() - retorna todas as ocorrências
todas_ocorrencias = re.findall(r"\b\w+\b", texto_exemplo)
print(todas_ocorrencias)
# re.finditer() - iterador sobre todas as ocorrências
for match in re.finditer(r"\b\w{5}\b", texto_exemplo):
print(f"Palavra: {match.group()}, posição: {match.start()}-{match.end()}")
4. Grupos, grupos nomeados e referências retroativas
Grupos de captura permitem extrair partes específicas de uma correspondência:
# Grupos de captura tradicionais
data = "15/03/2024"
padrao_data = r"(\d{2})/(\d{2})/(\d{4})"
match = re.search(padrao_data, data)
if match:
dia, mes, ano = match.groups()
print(f"Dia: {dia}, Mês: {mes}, Ano: {ano}")
# Grupos nomeados
padrao_nomeado = r"(?P<dia>\d{2})/(?P<mes>\d{2})/(?P<ano>\d{4})"
match_nomeado = re.search(padrao_nomeado, data)
if match_nomeado:
print(f"Dia: {match_nomeado.group('dia')}")
print(f"Mês: {match_nomeado.group('mes')}")
# Referências retroativas
texto_repetido = "casa casa"
padrao_repeticao = r"(\b\w+\b) \1"
match_repetido = re.search(padrao_repeticao, texto_repetido)
if match_repetido:
print(f"Palavra repetida: {match_repetido.group(1)}")
# Referência com grupo nomeado
padrao_nomeado_repetido = r"(?P<palavra>\b\w+\b) (?P=palavra)"
match_nomeado_repetido = re.search(padrao_nomeado_repetido, texto_repetido)
print(f"Palavra repetida (nomeada): {match_nomeado_repetido.group('palavra')}")
5. Substituição e divisão de strings
re.sub() e re.split() são poderosas ferramentas para manipulação de strings:
# Substituição básica
texto_original = "Telefone: 11-9999-8888 e 21-7777-6666"
texto_substituido = re.sub(r"\d{4}-\d{4}", "XXXX-XXXX", texto_original)
print(texto_substituido)
# Substituição com função
def mascarar_telefone(match):
return match.group(0)[:2] + "XXXX-XXXX"
texto_mascarado = re.sub(r"\d{2}-\d{4}-\d{4}", mascarar_telefone, texto_original)
print(texto_mascarado)
# re.subn() - retorna tupla com resultado e contagem
resultado, contagem = re.subn(r"\d", "#", "Telefone 1234")
print(f"Resultado: {resultado}, Substituições: {contagem}")
# re.split() com padrão complexo
texto_para_dividir = "maçã;banana, laranja|uva"
frutas = re.split(r"[;,\s|]+", texto_para_dividir)
print(frutas)
6. Lookahead e lookbehind (asserções de largura zero)
Asserções de largura zero permitem verificar condições sem consumir caracteres:
# Lookahead positivo
senhas = ["abc123", "123456", "abcde", "a1b2c3"]
padrao_senha = re.compile(r"^(?=.*[a-zA-Z])(?=.*\d).{6,}$")
for senha in senhas:
if padrao_senha.match(senha):
print(f"Senha válida: {senha}")
# Lookahead negativo
texto_sql = "SELECT * FROM usuarios WHERE id = 1; DROP TABLE usuarios;"
padrao_seguro = re.compile(r"^(?!.*DROP\s+TABLE).*$", re.IGNORECASE)
if padrao_seguro.match(texto_sql):
print("Comando SQL seguro")
else:
print("Comando SQL potencialmente perigoso")
# Lookbehind positivo
precos = "O preço é R$ 50,00 e o desconto é R$ 10,00"
padrao_preco = re.compile(r"(?<=R\$ )\d+,\d{2}")
precos_encontrados = padrao_preco.findall(precos)
print(f"Preços encontrados: {precos_encontrados}")
# Lookbehind negativo
texto_links = "Visite https://exemplo.com ou http://site.com"
padrao_https = re.compile(r"(?<!https:)//[^\s]+")
links_http = padrao_https.findall(texto_links)
print(f"Links HTTP: {links_http}")
7. Tratamento de erros e desempenho
# Tratamento de exceções
try:
padrao_invalido = re.compile(r"[a-z")
except re.error as e:
print(f"Erro na expressão regular: {e}")
# Uso de re.DEBUG para depuração
try:
re.compile(r"\d+\.\d+", re.DEBUG)
except:
pass # Saída de debug será mostrada
# Evitando backtracking catastrófico
# Padrão problemático (lento)
padrao_lento = r"(a+)+b"
# Padrão otimizado com grupo atômico (Python 3.11+)
padrao_otimizado = r"(?>a+)+b"
# Exemplo prático de otimização
texto_grande = "a" * 30 + "c"
import time
inicio = time.time()
match_lento = re.search(r"(a+)+b", texto_grande)
fim_lento = time.time()
print(f"Tempo do padrão lento: {fim_lento - inicio:.4f}s")
inicio = time.time()
match_rapido = re.search(r"(?>a+)+b", texto_grande)
fim_rapido = time.time()
print(f"Tempo do padrão otimizado: {fim_rapido - inicio:.4f}s")
8. Casos de uso práticos no ecossistema Python
# Validação de e-mail
def validar_email(email):
padrao = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return bool(re.match(padrao, email))
emails_teste = ["user@example.com", "inválido@", "teste@dominio.org"]
for email in emails_teste:
print(f"{email}: {'válido' if validar_email(email) else 'inválido'}")
# Validação de URL
def validar_url(url):
padrao = r"^https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+(?::\d+)?(?:/[-\w%.~+]*)*(?:\?[-\w&=%.~+]*)?(?:#[-\w]*)?$"
return bool(re.match(padrao, url, re.IGNORECASE))
# Parsing de logs
log_entries = """2024-03-15 10:30:45 ERROR: Conexão perdida
2024-03-15 10:31:12 INFO: Reconexão estabelecida
2024-03-15 10:32:00 WARNING: Latência alta"""
padrao_log = re.compile(
r"(?P<data>\d{4}-\d{2}-\d{2})\s+"
r"(?P<hora>\d{2}:\d{2}:\d{2})\s+"
r"(?P<nivel>ERROR|INFO|WARNING):\s+"
r"(?P<mensagem>.+)"
)
for match in padrao_log.finditer(log_entries):
print(f"Data: {match.group('data')}, "
f"Nível: {match.group('nivel')}, "
f"Mensagem: {match.group('mensagem')}")
# Limpeza e normalização de dados
dados_sujos = "Nome: João; Idade: 30; Email: joao@email.com"
dados_limpos = re.sub(r"[;:]", ",", dados_sujos)
dados_limpos = re.sub(r"\s+", " ", dados_limpos)
print(f"Dados normalizados: {dados_limpos}")
# Extração de números de telefone em diferentes formatos
telefones_mistos = """
(11) 99999-8888
21 98888-7777
31 9 7777-6666
"""
padrao_telefone = re.compile(r"""
\(?\d{2}\)? # código de área com ou sem parênteses
\s* # espaço opcional
9? # dígito 9 opcional
\s* # espaço opcional
\d{4} # quatro primeiros dígitos
-?\s* # hífen opcional
\d{4} # quatro últimos dígitos
""", re.VERBOSE)
telefones_encontrados = padrao_telefone.findall(telefones_mistos)
for tel in telefones_encontrados:
print(f"Telefone encontrado: {tel.strip()}")
Referências
- Documentação oficial do módulo
re— Referência completa com todas as funções, flags e sintaxe do móduloreem Python - Python Regex HOWTO — Tutorial oficial da documentação Python com exemplos práticos de expressões regulares
- Real Python: Regular Expressions in Python — Guia abrangente com exemplos práticos e casos de uso do mundo real
- Regex101: Python Flavor — Ferramenta interativa para testar e depurar expressões regulares com suporte ao motor regex do Python
- GeeksforGeeks: Python Regex Tutorial — Tutorial detalhado com exemplos variados de expressões regulares em Python