Expressões regulares em Python
LidoNesta aula, você aprenderá a usar expressões regulares em Python através do módulo re. Exploraremos as funções match, search e findall, o uso de grupos para capturar partes específicas do texto e a compilação de padrões para otimizar o desempenho. Ao final, você terá uma base sólida para aplicar regex em seus projetos.
Expressões regulares (regex) são uma ferramenta poderosa para busca e manipulação de texto baseada em padrões. Em Python, o módulo re fornece todas as funcionalidades necessárias para trabalhar com regex. Nesta aula, você aprenderá os conceitos fundamentais e as funções mais utilizadas, com exemplos práticos que demonstram como aplicar cada uma delas.
Dominar regex é essencial para tarefas como validação de dados, extração de informações de logs, limpeza de texto e muito mais. Vamos começar explorando o módulo re e suas principais funções, seguindo para técnicas mais avançadas como grupos e compilação.
Módulo re
O módulo re é a biblioteca padrão do Python para trabalhar com expressões regulares. Ele fornece funções e classes que permitem buscar, substituir e dividir strings usando padrões. Para utilizá-lo, basta importar o módulo com import re.
As funções mais comuns do módulo são match(), search(), findall(), finditer(), sub(), split() e compile(). Cada uma tem um propósito específico, e escolher a função certa é crucial para obter o resultado desejado.
import re
# Exemplo simples: procurar um padrão em uma string
padrao = r"\d+" # um ou mais dígitos
texto = "A temperatura é 25°C e a umidade 60%."
# Encontrar todos os números
numeros = re.findall(padrao, texto)
print(numeros) # Saída: ['25', '60']No exemplo acima, usamos uma string bruta (prefixo r) para definir o padrão, evitando problemas com escapes. A função findall retorna uma lista com todas as ocorrências não sobrepostas do padrão na string.
match, search e findall
As funções match(), search() e findall() são as mais utilizadas para buscar padrões em strings, mas diferem na forma como realizam a busca.
match() verifica se o padrão ocorre no início da string. Ela retorna um objeto match se o padrão for encontrado no começo, ou None caso contrário. É importante notar que match() não procura em outras posições.
import re
padrao = r"\d+"
texto1 = "123abc"
texto2 = "abc123"
m1 = re.match(padrao, texto1)
m2 = re.match(padrao, texto2)
print(m1) # <re.Match object; span=(0, 3), match='123'>
print(m2) # Nonesearch() procura o padrão em qualquer parte da string, não apenas no início. Ela retorna o primeiro objeto match encontrado, ou None se não houver correspondência.
import re
padrao = r"\d+"
texto = "abc123def"
s = re.search(padrao, texto)
if s:
print(s.group()) # Saída: '123'
findall() retorna todas as ocorrências do padrão na string como uma lista de strings. Se o padrão contiver grupos, o retorno será uma lista de tuplas com os grupos capturados.
import re
padrao = r"\d+"
texto = "abc123def456ghi"
numeros = re.findall(padrao, texto)
print(numeros) # Saída: ['123', '456']Além dessas, existe finditer(), que retorna um iterador de objetos match, útil para processar grandes volumes de texto com eficiência.
Grupos
Grupos permitem capturar partes específicas da correspondência. Eles são definidos com parênteses () no padrão. Ao usar grupos, você pode extrair informações individuais de um texto estruturado.
Por exemplo, para capturar o dia, mês e ano de uma data no formato DD/MM/AAAA, podemos usar o padrão (\d{2})/(\d{2})/(\d{4}). Cada grupo corresponde a uma parte da data.
import re
data = "15/08/2024"
padrao = r"(\d{2})/(\d{2})/(\d{4})"
m = re.match(padrao, data)
if m:
dia, mes, ano = m.groups()
print(f"Dia: {dia}, Mês: {mes}, Ano: {ano}")
# Saída: Dia: 15, Mês: 08, Ano: 2024
Você também pode acessar grupos individuais pelo índice com m.group(1), m.group(2), etc. O grupo 0 é a correspondência inteira.
Grupos nomeados são uma alternativa legível: (?P<nome>...). Eles permitem acessar os valores pelo nome em vez do número.
import re
padrao = r"(?P<dia>\d{2})/(?P<mes>\d{2})/(?P<ano>\d{4})"
data = "15/08/2024"
m = re.match(padrao, data)
if m:
print(m.group("dia")) # Saída: 15
print(m.group("mes")) # Saída: 08
print(m.group("ano")) # Saída: 2024
Além de capturar, grupos também servem para agrupar partes do padrão para aplicar quantificadores, como (ab)+ para uma ou mais repetições de "ab".
Compilação
Quando você usa funções como re.match() ou re.search(), o Python compila o padrão em um objeto interno a cada chamada. Se você for usar o mesmo padrão repetidamente, é mais eficiente compilá-lo uma única vez com re.compile() e reutilizar o objeto compilado.
Além de melhorar o desempenho, compilar permite definir flags (como re.IGNORECASE) e reutilizar o padrão em múltiplas operações.
import re
# Compilar o padrão com a flag IGNORECASE
padrao_compilado = re.compile(r"python", re.IGNORECASE)
texto1 = "Eu gosto de Python."
texto2 = "PYTHON é ótimo."
# Usar o padrão compilado
m1 = padrao_compilado.search(texto1)
m2 = padrao_compilado.search(texto2)
print(m1.group() if m1 else "Não encontrado") # Saída: Python
print(m2.group() if m2 else "Não encontrado") # Saída: PYTHON
O objeto compilado tem os mesmos métodos (match, search, findall, etc.) e propriedades. Isso torna o código mais limpo e legível, especialmente quando o padrão é complexo.
Outra vantagem é que você pode definir flags na compilação, como re.MULTILINE para tratar cada linha separadamente ou re.DOTALL para o ponto corresponder a qualquer caractere, incluindo quebras de linha.
import re
padrao = re.compile(r"^\d+", re.MULTILINE)
texto = "12 abc\n345 def\n678"
# Encontra números no início de cada linha
print(padrao.findall(texto)) # Saída: ['12', '345', '678']Em resumo, compile padrões que serão usados muitas vezes ou que precisam de flags específicas. Para usos pontuais, as funções diretas são suficientes.
Boas práticas e observações finais
Ao trabalhar com expressões regulares, é recomendável usar strings brutas (r"...") para evitar conflitos com caracteres de escape do Python. Além disso, comece com padrões simples e refine gradualmente, testando com findall() ou finditer() para ver as correspondências.
Lembre-se de que regex pode se tornar complexa; prefira padrões legíveis e comente-os quando necessário. Para validações críticas, considere bibliotecas específicas (ex.: email-validator) em vez de reinventar a roda. E, acima de tudo, teste com casos extremos para garantir que seu padrão se comporta como esperado.
Referências
- Documentação oficial do módulo re (Python)
- HOWTO Regex em Python
- Regex101 - Testador de regex online
- Regular-Expressions.info - Tutorial completo
- MDN - Guia de Expressões Regulares (referência geral)
- W3Schools - Python RegEx
Exercícios
- Escreva uma função que receba uma string e retorne todas as palavras que contêm exatamente 5 letras, usando expressões regulares. Considere que palavras são separadas por espaços e não há pontuação.
- Dada a string "João nasceu em 1990 e Maria em 1995", extraia todos os anos (4 dígitos) e calcule a média deles.
- Valide se uma string é um número de telefone no formato (XX) XXXXX-XXXX, onde X é um dígito. Use
match()para garantir que começa com o padrão e termina após ele. - Extraia o domínio de um e-mail. Escreva uma função que receba uma string com um e-mail e retorne o domínio (parte após o @).
- Use
re.compile()para criar um padrão que encontre todas as palavras que começam com a letra "s" (case insensitive) em um texto. Teste com o texto: "Sopa, salada e suco são saudáveis."
import re
def palavras_5_letras(texto):
# \b é uma borda de palavra, \w{5} são 5 caracteres alfanuméricos
padrao = r"\b\w{5}\b"
return re.findall(padrao, texto)
print(palavras_5_letras("O rato roeu a roupa do rei")) # Saída: ['roupa']import re
texto = "João nasceu em 1990 e Maria em 1995"
anos = re.findall(r"\b\d{4}\b", texto)
anos_int = [int(ano) for ano in anos]
media = sum(anos_int) / len(anos_int) if anos_int else 0
print(f"Anos: {anos_int}, Média: {media}") # Saída: Anos: [1990, 1995], Média: 1992.5import re
def validar_telefone(telefone):
padrao = r"^\(\d{2}\) \d{5}-\d{4}$"
return re.match(padrao, telefone) is not None
print(validar_telefone("(11) 98765-4321")) # True
print(validar_telefone("11) 98765-4321")) # Falseimport re
def extrair_dominio(email):
padrao = r"@([\w.-]+)"
m = re.search(padrao, email)
return m.group(1) if m else None
print(extrair_dominio("usuario@example.com")) # Saída: example.comimport re
padrao = re.compile(r"\bs\w*", re.IGNORECASE)
texto = "Sopa, salada e suco são saudáveis."
palavras = padrao.findall(texto)
print(palavras) # Saída: ['Sopa', 'salada', 'suco', 'são', 'saudáveis']