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)  # None

search() 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

Exercícios

  1. 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.
  2. ✓ Resposta:
    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']
  3. 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.
  4. ✓ Resposta:
    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.5
  5. 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.
  6. ✓ Resposta:
    import 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"))   # False
  7. 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 @).
  8. ✓ Resposta:
    import 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.com
  9. 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."
  10. ✓ Resposta:
    import 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']