Expressões Regulares em Python: regex com o módulo re

Cansado de programar?

Conheça a melhor e mais completa formação de Python e Django e sinta-se um programador verdadeiramente competente. Além de Python e Django, você também vai aprender Banco de Dados, SQL, HTML, CSS, Javascript, Bootstrap e muito mais!

Quero aprender Python e Django de Verdade! Quero aprender!
Suporte

Tire suas dúvidas diretamente com o professor

Projetos práticos

Projetos práticos voltados para o mercado de trabalho

Prática profissional

Formação moderna com foco na prática profissional

Resposta rápida

Expressões regulares (regex) são padrões para buscar, validar e substituir texto. Em Python você usa o módulo re: re.search() encontra, re.findall() lista todas as ocorrências, re.fullmatch() valida e re.sub() substitui. Escreva o padrão como raw string: r"\d{5}-\d{3}".

1
2
3
4
5
import re

texto = "Entregar no CEP 01310-100 até 12/03/2026"
print(re.search(r"\d{5}-\d{3}", texto).group())  # 01310-100
print(re.findall(r"\d+", texto))  # ['01310', '100', '12', '03', '2026']

Resumo em 30 segundos:

  • search() acha em qualquer posição; match() só no início; fullmatch() exige a string inteira.
  • Sem ocorrência, search(), match() e fullmatch() devolvem None.
  • Use sempre r"..." (raw string) para o padrão.
  • Parênteses criam grupos; (?P<nome>...) cria grupos nomeados.
  • Regex valida formato, não se o CPF, e-mail ou data existem de verdade.

Salve salve Pythonista!

Extrair todos os e-mails de um texto, conferir se o usuário digitou o CEP no formato certo, trocar datas de dd/mm/aaaa para aaaa-mm-dd em um arquivo inteiro: tarefas assim viram dezenas de linhas com split() e if, ou uma linha com expressões regulares.

Neste guia você vai aprender o módulo re do Python: as funções principais, os metacaracteres, grupos, flags e padrões compilados, e vai ver exemplos brasileiros testados (CPF, CEP, telefone, e-mail e data). Todos os códigos foram executados no Python 3.13 e também conferidos no 3.10, com as saídas reais.

Então… Bora pro post! :rocket:

Vá Direto ao Assunto…

O que são expressões regulares

O que é uma expressão regular? Uma expressão regular (regex ou regexp) é uma sequência de caracteres que descreve um padrão de texto. Em vez de procurar uma palavra fixa, você descreve a forma do que procura, por exemplo “cinco dígitos, um hífen e três dígitos” (\d{5}-\d{3}), e o motor de regex encontra todos os trechos que seguem esse padrão.

Em Python, as expressões regulares ficam no módulo re, que já vem na biblioteca padrão. Basta import re. A referência completa está na documentação oficial do módulo re, e há um bom tutorial no Regular Expression HOWTO.

Regex trabalha sobre strings, então vale ter os métodos de texto frescos na memória: veja o nosso guia de strings no Python. Muitas vezes um in, startswith() ou replace() resolve sem regex, e é mais legível.

Raw strings: sempre use r”…”

Regex usa muita barra invertida (\d, \w, \b). O problema é que o Python também interpreta barras invertidas em strings comuns: "\b" vira o caractere backspace antes mesmo de chegar ao módulo re. Veja:

1
2
3
4
import re

print(re.search("\bpython\b", "eu uso python"))
print(re.search(r"\bpython\b", "eu uso python"))
1
2
None
<re.Match object; span=(7, 13), match='python'>

A primeira busca falha em silêncio. Com o prefixo r (raw string), a barra chega intacta ao re. Regra prática: todo padrão de regex começa com r.

As funções do módulo re

re.search(): encontrar a primeira ocorrência

re.search(padrao, texto) percorre o texto e devolve um objeto Match com a primeira ocorrência, ou None se não achar nada:

1
2
3
4
5
6
7
8
9
import re

texto = "Pedido 4521 enviado em 12/03/2026 para o CEP 01310-100."

resultado = re.search(r"\d{5}-\d{3}", texto)
print(resultado)
print(resultado.group())
print(resultado.start(), resultado.end())
print(re.search(r"\d{5}-\d{3}", "sem cep aqui"))
1
2
3
4
<re.Match object; span=(45, 54), match='01310-100'>
01310-100
45 54
None

O group() devolve o texto encontrado; start() e end() dão a posição. Como o retorno pode ser None, sempre teste antes de chamar .group() (veja em Erros comuns).

re.match() e re.fullmatch(): início e string inteira

As três funções de busca diferem só em onde o padrão precisa casar:

1
2
3
4
5
6
7
8
import re

print(re.match(r"\d+", "4521 itens"))
print(re.match(r"\d+", "Pedido 4521"))
print(re.search(r"\d+", "Pedido 4521"))
print(re.fullmatch(r"\d{5}-\d{3}", "01310-100"))
print(re.fullmatch(r"\d{5}-\d{3}", "01310-1000"))
print(re.search(r"\d{5}-\d{3}", "01310-1000"))
1
2
3
4
5
6
<re.Match object; span=(0, 4), match='4521'>
None
<re.Match object; span=(7, 11), match='4521'>
<re.Match object; span=(0, 9), match='01310-100'>
None
<re.Match object; span=(0, 9), match='01310-100'>
  • match() só aceita o padrão no começo da string: "Pedido 4521" começa com letra, então dá None.
  • fullmatch() exige que a string inteira case. "01310-1000" tem um dígito a mais e é rejeitado.
  • Repare na última linha: search() aceita "01310-1000", porque encontra um CEP dentro dele. Por isso, para validar, use fullmatch().

re.findall() e re.finditer(): todas as ocorrências

findall() devolve uma lista com tudo o que casou. Se o padrão tiver grupos (parênteses), a lista traz os grupos em vez do trecho inteiro:

1
2
3
4
5
6
7
8
9
import re

texto = "Contatos: (11) 98765-4321, (21) 3456-7890 e (31) 99999-0000."

print(re.findall(r"\(\d{2}\)", texto))
print(re.findall(r"\((\d{2})\) (\d{4,5}-\d{4})", texto))

for m in re.finditer(r"\((\d{2})\)", texto):
    print(m.group(1), "na posição", m.start())
1
2
3
4
5
['(11)', '(21)', '(31)']
[('11', '98765-4321'), ('21', '3456-7890'), ('31', '99999-0000')]
11 na posição 10
21 na posição 27
31 na posição 44

finditer() devolve um iterador de objetos Match, útil quando você precisa da posição, de grupos nomeados, ou quando o texto é grande e não convém montar uma lista inteira na memória.

re.sub() e re.split(): substituir e dividir

re.sub(padrao, substituto, texto) troca todas as ocorrências. No substituto, \1, \2… referenciam os grupos capturados. re.split() divide o texto usando o padrão como separador:

1
2
3
4
5
6
import re

print(re.sub(r"\s+", " ", "Python    é   muito    bom"))
print(re.sub(r"\d", "*", "Cartão 1234 5678"))
print(re.sub(r"(\d{2})/(\d{2})/(\d{4})", r"\3-\2-\1", "Vence em 25/12/2026"))
print(re.split(r"[;,]\s*", "maçã, banana;uva,  pera"))
1
2
3
4
Python é muito bom
Cartão **** ****
Vence em 2026-12-25
['maçã', 'banana', 'uva', 'pera']

O str.split() comum aceita só um separador fixo; o re.split() aceita “vírgula ou ponto e vírgula, seguidos de espaços opcionais”. Se você for montar o resultado em uma mensagem, as f-strings no Python combinam bem com os grupos capturados.

Metacaracteres: a tabela de referência

Estes são os símbolos que você mais vai usar. Qualquer outro caractere casa com ele mesmo.

Símbolo Significado Exemplo Casa com
. Qualquer caractere, exceto quebra de linha c.sa casa, cosa, c3sa
\d Um dígito (0-9) \d\d 42
\D Qualquer coisa que não seja dígito \D+ abc
\w Letra, dígito ou _ (inclui acentos) \w+ ação, x_1
\s Espaço, tab ou quebra de linha a\sb a b
\b Fronteira de palavra \bgato\b gato (não gatos)
^ Início da string (ou da linha, com re.M) ^Olá Olá no começo
$ Fim da string (ou da linha, com re.M) fim$ fim no final
* 0 ou mais repetições ab* a, ab, abbb
+ 1 ou mais repetições \d+ 7, 2026
? 0 ou 1 (opcional) colou?r color, colour
{n} / {n,m} Exatamente n / de n a m repetições \d{4,5} 3456, 98765
[abc] Um caractere do conjunto [aeiou] uma vogal
[^abc] Um caractere fora do conjunto [^0-9] não dígito
[a-z] Um caractere do intervalo [A-Z]{3} ABC
| Alternativa (ou) jpg|png jpg ou png
( ) Grupo de captura (\d{2})/ captura o dia
(?: ) Grupo sem captura (?:ab)+ abab
\ Escapa um metacaractere R\$ \d+ R$ 50

Para buscar um metacaractere literal (ponto, parênteses, cifrão), escape-o com \: \., \(, \$. Se o texto vier do usuário, re.escape() faz isso por você: re.escape("R$ 5.00") devolve R\$\ 5\.00.

Os quantificadores *, + e {n,m} são gulosos: pegam o máximo possível. Acrescentar ? (*?, +?) os torna preguiçosos, pegando o mínimo. Isso faz toda a diferença, como você verá em Erros comuns.

Grupos e grupos nomeados

Parênteses fazem duas coisas: agrupam partes do padrão e capturam o trecho correspondente, que você acessa por número:

1
2
3
4
5
6
import re

m = re.search(r"(\d{2})/(\d{2})/(\d{4})", "Nascimento: 07/09/1995")
print(m.group())
print(m.group(1), m.group(2), m.group(3))
print(m.groups())
1
2
3
07/09/1995
07 09 1995
('07', '09', '1995')

Com vários grupos, lembrar que o 3 é o ano fica difícil. Grupos nomeados, com a sintaxe (?P<nome>...), resolvem:

1
2
3
4
5
6
import re

m = re.search(r"(?P<dia>\d{2})/(?P<mes>\d{2})/(?P<ano>\d{4})", "Nascimento: 07/09/1995")
print(m.group("ano"))
print(m["mes"])
print(m.groupdict())
1
2
3
1995
09
{'dia': '07', 'mes': '09', 'ano': '1995'}

Quando você precisa agrupar só para repetir ou alternar, sem capturar, use (?:...). Isso é importante no findall(), que devolveria só o grupo:

1
2
3
import re

print(re.findall(r"(?:\d{3}\.){2}\d{3}", "IDs: 123.456.789 e 987.654.321"))
1
['123.456.789', '987.654.321']

Está curtindo esse conteúdo? :thumbsup:

Que tal receber 30 dias de conteúdo direto na sua Caixa de Entrada?

Sua assinatura não pôde ser validada.
Você fez sua assinatura com sucesso.

Assine as PyDicas e receba 30 dias do melhor conteúdo Python na sua Caixa de Entrada: direto e sem enrolação!

Flags e padrões compilados

Flags: mudando o comportamento da busca

Flags são passadas no argumento flags= (e podem ser combinadas com o operador ou bit a bit). As mais usadas:

1
2
3
4
5
6
7
8
9
10
11
12
import re

print(re.findall(r"python", "Python, PYTHON e python", flags=re.IGNORECASE))

log = """ERRO: disco cheio
INFO: backup ok
ERRO: timeout"""
print(re.findall(r"^ERRO: (.+)$", log, flags=re.MULTILINE))
print(re.findall(r"^ERRO: (.+)$", log))

print(re.search(r"inicio.*fim", "inicio\nmeio\nfim"))
print(re.search(r"inicio.*fim", "inicio\nmeio\nfim", flags=re.DOTALL))
1
2
3
4
5
['Python', 'PYTHON', 'python']
['disco cheio', 'timeout']
[]
None
<re.Match object; span=(0, 15), match='inicio\nmeio\nfim'>
Flag Atalho O que faz
re.IGNORECASE re.I Ignora maiúsculas e minúsculas
re.MULTILINE re.M ^ e $ passam a valer para cada linha, não só para a string toda
re.DOTALL re.S O . passa a casar também com a quebra de linha
re.VERBOSE re.X Permite espaços e comentários dentro do padrão
re.ASCII re.A \w, \d e \s casam só com ASCII (sem acentos)

Sobre o re.ASCII: em Python 3, \w aceita letras acentuadas por padrão. re.findall(r"\w+", "ação café") devolve ['ação', 'café'], mas com re.ASCII devolve ['a', 'o', 'caf'].

re.compile(): reutilizando o padrão

Se você usa o mesmo padrão várias vezes, compile-o uma vez com re.compile(). O objeto Pattern tem os mesmos métodos (search, findall, sub…) e deixa o código mais legível, com o padrão nomeado em uma constante:

1
2
3
4
5
6
7
8
9
10
11
12
import re

CEP = re.compile(r"\d{5}-\d{3}")

enderecos = [
    "Av. Paulista, 1000 - CEP 01310-100",
    "Rua sem número",
    "Praça da Sé, CEP 01001-000",
]
for endereco in enderecos:
    m = CEP.search(endereco)
    print(m.group() if m else "CEP não encontrado")
1
2
3
01310-100
CEP não encontrado
01001-000

Combinado com re.VERBOSE, o padrão fica autoexplicativo:

1
2
3
4
5
6
7
8
9
import re

padrao_cep = re.compile(r"""
    (?P<prefixo>\d{5})   # cinco primeiros dígitos
    -?                   # hífen opcional
    (?P<sufixo>\d{3})    # três últimos dígitos
""", re.VERBOSE)
print(padrao_cep.fullmatch("01310-100").groupdict())
print(padrao_cep.fullmatch("01310100").groupdict())
1
2
{'prefixo': '01310', 'sufixo': '100'}
{'prefixo': '01310', 'sufixo': '100'}

O módulo re guarda em cache os padrões usados recentemente, então compilar é mais uma questão de organização do que de velocidade em scripts pequenos.

Exemplos brasileiros: CPF, CEP, telefone, e-mail e data

Aviso importante: regex valida o formato, não a validade real. Uma regex diz se o texto “parece” um CPF, não se o CPF existe; diz se a data tem cara de dd/mm/aaaa, não se o dia existe no calendário. Para isso, combine a regex com uma verificação em código, como nos exemplos abaixo.

Todos usam fullmatch(), porque estamos validando o campo inteiro.

1
2
3
4
5
6
7
8
9
import re

CPF = re.compile(r"\d{3}\.\d{3}\.\d{3}-\d{2}")
for cpf in ["529.982.247-25", "52998224725", "529.982.247-2", "111.111.111-11"]:
    print(cpf, bool(CPF.fullmatch(cpf)))

CEP = re.compile(r"\d{5}-?\d{3}")
for cep in ["01310-100", "01310100", "1310-100", "01310-1000"]:
    print(cep, bool(CEP.fullmatch(cep)))
1
2
3
4
5
6
7
8
529.982.247-25 True
52998224725 False
529.982.247-2 False
111.111.111-11 True
01310-100 True
01310100 True
1310-100 False
01310-1000 False

Repare que 111.111.111-11 passa na regex, mas não é um CPF válido. O CPF de exemplo 529.982.247-25 é um número gerado para testes. Para conferir de verdade, calcule os dígitos verificadores:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import re

CPF = re.compile(r"\d{3}\.\d{3}\.\d{3}-\d{2}")


def cpf_valido(cpf: str) -> bool:
    if not CPF.fullmatch(cpf):
        return False
    digitos = [int(d) for d in re.sub(r"\D", "", cpf)]
    if len(set(digitos)) == 1:
        return False
    for tamanho in (9, 10):
        soma = sum(d * peso for d, peso in zip(digitos, range(tamanho + 1, 1, -1)))
        dv = (soma * 10) % 11 % 10
        if dv != digitos[tamanho]:
            return False
    return True


for cpf in ["529.982.247-25", "529.982.247-26", "111.111.111-11"]:
    print(cpf, cpf_valido(cpf))
1
2
3
529.982.247-25 True
529.982.247-26 False
111.111.111-11 False

A regex filtra o formato, o re.sub(r"\D", "", cpf) remove tudo que não é dígito e o laço calcula os dois dígitos verificadores pelo módulo 11. CPFs com todos os dígitos iguais são rejeitados à parte. Mesmo assim, isso prova só que o número é matematicamente válido, não que pertence a alguém.

Telefone com DDD (fixo ou celular, com ou sem parênteses e hífen) e e-mail:

1
2
3
4
5
6
7
8
9
10
import re

TELEFONE = re.compile(r"\(?(?P<ddd>\d{2})\)?\s?(?P<numero>9?\d{4}-?\d{4})")
for tel in ["(11) 98765-4321", "11987654321", "(21) 3456-7890", "98765-4321"]:
    m = TELEFONE.fullmatch(tel)
    print(tel, m.groupdict() if m else None)

EMAIL = re.compile(r"[\w.+-]+@[\w-]+(?:\.[\w-]+)+")
for email in ["[email protected]", "[email protected]", "sem-arroba.com", "ana@exemplo", "ana@@exemplo.com"]:
    print(email, bool(EMAIL.fullmatch(email)))
1
2
3
4
5
6
7
8
9
(11) 98765-4321 {'ddd': '11', 'numero': '98765-4321'}
11987654321 {'ddd': '11', 'numero': '987654321'}
(21) 3456-7890 {'ddd': '21', 'numero': '3456-7890'}
98765-4321 None
[email protected] True
[email protected] True
sem-arroba.com False
ana@exemplo False
ana@@exemplo.com False

A regex de e-mail é propositalmente simples: pega os erros de digitação mais comuns, mas não cobre todas as regras da especificação e não garante que a caixa de e-mail existe. A única forma de confirmar um e-mail é enviar uma mensagem de confirmação. Da mesma forma, a regex de telefone aceita qualquer DDD de dois dígitos, inclusive os que não existem.

Por fim, a data dd/mm/aaaa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import re
from datetime import datetime

DATA = re.compile(r"(0[1-9]|[12]\d|3[01])/(0[1-9]|1[0-2])/(\d{4})")
for data in ["07/09/2026", "7/9/2026", "32/01/2026", "15/13/2026", "31/02/2026"]:
    print(data, bool(DATA.fullmatch(data)))


def data_valida(texto: str) -> bool:
    if not DATA.fullmatch(texto):
        return False
    try:
        datetime.strptime(texto, "%d/%m/%Y")
    except ValueError:
        return False
    return True


for d in ["29/02/2024", "29/02/2026", "31/04/2026", "31/12/2026"]:
    print(d, data_valida(d))
1
2
3
4
5
6
7
8
9
07/09/2026 True
7/9/2026 False
32/01/2026 False
15/13/2026 False
31/02/2026 True
29/02/2024 True
29/02/2026 False
31/04/2026 False
31/12/2026 True

A regex barra dia 32 e mês 13, mas aceita 31/02/2026, que não existe. O datetime.strptime() completa a validação: ele levanta ValueError: day is out of range for month para datas impossíveis e sabe que 2024 é bissexto e 2026 não. Se try/except ainda é novidade, veja o post sobre tratamento de erros e exceções no Python.

Regex resolve muita validação de texto, mas a base que sustenta esse tipo de raciocínio - funções, estruturas de dados, tratamento de erros - é o que a Jornada Python ensina, com projetos guiados do zero ao avançado:

Qual função do re usar?

Você quer… Use Retorno
Saber se o padrão aparece em algum lugar re.search() Match ou None
Checar se o texto começa com o padrão re.match() Match ou None
Validar um campo inteiro (CEP, CPF, data) re.fullmatch() Match ou None
Pegar todas as ocorrências como texto re.findall() list de str (ou de tuplas, com grupos)
Percorrer ocorrências com posição e grupos re.finditer() Iterador de Match
Substituir ou reformatar trechos re.sub() Nova str
Dividir por vários separadores re.split() list de str
Usar o mesmo padrão muitas vezes re.compile() Objeto Pattern
Buscar um texto fixo, sem padrão in, str.find(), str.replace() Sem regex!

Erros comuns

Estes são os erros que mais aparecem com regex em Python, com a mensagem real do Python 3.13.

Esquecer o r da raw string (SyntaxWarning)

1
2
3
import re

print(re.findall("\d+", "a1b22"))
1
SyntaxWarning: invalid escape sequence '\d'

O resultado até sai certo (['1', '22']), mas desde o Python 3.12 o aviso aparece, e em versões futuras deve virar erro. Pior: com "\b" não há aviso nenhum e a busca simplesmente não encontra nada, como vimos no início. Correção: re.findall(r"\d+", "a1b22").

Chamar .group() quando não houve correspondência (AttributeError)

1
2
3
4
import re

m = re.match(r"\d{5}-\d{3}", "CEP: 01310-100")
print(m.group())
1
AttributeError: 'NoneType' object has no attribute 'group'

Dois problemas: match() só procura no início (e o texto começa com “CEP:”), e o código não testa o None. Correção: use search() e verifique antes: if m: print(m.group()).

Parêntese sem fechar ou quantificador solto (PatternError)

1
2
3
import re

re.search(r"(\d{3}", "123")
1
re.PatternError: missing ), unterminated subpattern at position 0

No Python 3.12 ou anterior, a mesma exceção aparece como re.error (a partir do 3.13 ela se chama re.PatternError, e re.error continua como apelido). Um * ou + no começo do padrão dá nothing to repeat at position 0. Correção: feche os grupos e escape os metacaracteres que quer buscar literalmente, como \( e \*.

Quantificador guloso pegando texto demais

1
2
3
4
5
import re

html = "<b>Python</b> e <b>regex</b>"
print(re.findall(r"<b>(.*)</b>", html))
print(re.findall(r"<b>(.*?)</b>", html))
1
2
['Python</b> e <b>regex']
['Python', 'regex']

O .* vai até o último </b> que encontrar. Correção: use a versão preguiçosa .*?, que para no primeiro </b>. (Para HTML de verdade, prefira uma biblioteca como o BeautifulSoup.)

Validar com search() em vez de fullmatch()

1
2
3
4
import re

print(re.search(r"\d{5}-\d{3}", "CEP 012345-6789"))
print(re.fullmatch(r"\d{5}-\d{3}", "012345-6789"))
1
2
<re.Match object; span=(5, 14), match='12345-678'>
None

O search() encontra um “CEP” no meio de um valor inválido e o programa aceita o campo. Correção: para validar, use fullmatch() (ou ancore o padrão com ^...$).

Exercícios resolvidos

Tente resolver cada exercício antes de abrir a solução. Todos os códigos foram executados e as saídas conferidas. Para praticar mais manipulação de texto, veja a nossa página de exercícios de Python resolvidos.

Exercício 1. Extraia todos os números da frase "Comprei 3 cadernos por 45 reais e 12 canetas" como inteiros e some-os.

Ver solução
1
2
3
4
5
6
import re

texto = "Comprei 3 cadernos por 45 reais e 12 canetas"
numeros = [int(n) for n in re.findall(r"\d+", texto)]
print(numeros)
print(sum(numeros))

Saída: [3, 45, 12] e 60

O findall() devolve strings; a list comprehension converte cada uma com int(). O + garante que 45 venha inteiro, e não como 4 e 5.

Exercício 2. Liste as hashtags (sem o #) do texto "Aprendendo #python e #regex hoje! #DevBR".

Ver solução
1
2
3
4
import re

post = "Aprendendo #python e #regex hoje! #DevBR"
print(re.findall(r"#(\w+)", post))

Saída: ['python', 'regex', 'DevBR']

Como o padrão tem um grupo, o findall() devolve só o conteúdo do grupo, que é justamente o texto depois do #.

Exercício 3. Anonimize os CPFs de um texto, mantendo só os dois últimos dígitos: 529.982.247-25 deve virar ***.***.***-25.

Ver solução
1
2
3
4
import re

texto = "Cliente Ana, CPF 529.982.247-25, e cliente Bruno, CPF 123.456.789-09."
print(re.sub(r"\d{3}\.\d{3}\.\d{3}-(\d{2})", r"***.***.***-\1", texto))

Saída: Cliente Ana, CPF ***.***.***-25, e cliente Bruno, CPF ***.***.***-09.

O grupo (\d{2}) captura os dígitos finais e o \1 os devolve no substituto. No padrão, os pontos são escapados (\.); no substituto, não precisam.

Exercício 4. Usando grupos nomeados, converta todas as datas de dd/mm/aaaa para aaaa-mm-dd no texto "Início: 07/09/2026. Fim: 25/12/2026.".

Ver solução
1
2
3
4
5
import re

texto = "Início: 07/09/2026. Fim: 25/12/2026."
padrao = r"(?P<dia>\d{2})/(?P<mes>\d{2})/(?P<ano>\d{4})"
print(re.sub(padrao, r"\g<ano>-\g<mes>-\g<dia>", texto))

Saída: Início: 2026-09-07. Fim: 2026-12-25.

No substituto, grupos nomeados são referenciados com \g<nome>. O código fica mais legível que \3-\2-\1.

Exercício 5. Valide placas de carro brasileiras no padrão antigo (ABC-1234 ou ABC1234) e no padrão Mercosul (BRA2E19: três letras, um dígito, uma letra e dois dígitos). Teste com "ABC-1234", "ABC1234", "BRA2E19", "AB12345" e "bra2e19".

Ver solução
1
2
3
4
5
6
import re

PLACA = re.compile(r"[A-Z]{3}-?\d{4}|[A-Z]{3}\d[A-Z]\d{2}")

for placa in ["ABC-1234", "ABC1234", "BRA2E19", "AB12345", "bra2e19"]:
    print(placa, bool(PLACA.fullmatch(placa)))

Saída:

1
2
3
4
5
ABC-1234 True
ABC1234 True
BRA2E19 True
AB12345 False
bra2e19 False

A barra vertical separa as duas alternativas, e o fullmatch() exige que a placa inteira case com uma delas. Minúsculas são rejeitadas; para aceitá-las, use flags=re.IGNORECASE ou converta com .upper() antes.

Exercício 6. Transforme linhas de log no formato 2026-09-27 10:15:02 ERROR Falha ao conectar no banco em dicionários com data, hora, nivel e mensagem, ignorando linhas fora do formato.

Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
13
import re

logs = [
    "2026-09-27 10:15:02 ERROR Falha ao conectar no banco",
    "2026-09-27 10:15:09 INFO Nova tentativa",
    "linha corrompida",
]
padrao = re.compile(
    r"(?P<data>\d{4}-\d{2}-\d{2}) (?P<hora>\d{2}:\d{2}:\d{2}) (?P<nivel>[A-Z]+) (?P<mensagem>.+)"
)
for linha in logs:
    m = padrao.fullmatch(linha)
    print(m.groupdict() if m else "ignorada")

Saída:

1
2
3
{'data': '2026-09-27', 'hora': '10:15:02', 'nivel': 'ERROR', 'mensagem': 'Falha ao conectar no banco'}
{'data': '2026-09-27', 'hora': '10:15:09', 'nivel': 'INFO', 'mensagem': 'Nova tentativa'}
ignorada

Grupos nomeados mais groupdict() transformam texto em dados estruturados em uma linha. Esse é um dos usos mais comuns de regex no dia a dia.

Exercício 7 (estilo prova). Qual é a saída de print(re.match(r"\d+", "abc123"))?

a) 123
b) None
c) ['123']
d) re.PatternError

Ver solução

Saída: None

Resposta: b. O re.match() só testa o padrão a partir do início da string, e "abc123" começa com letras. Para encontrar o 123, seria preciso re.search(). A alternativa (c) seria o resultado de re.findall().

Exercício 8 (estilo prova). Considere re.fullmatch(r"\d{5}-?\d{3}", cep). Qual valor de cep não é aceito?

a) "01310-100"
b) "01310100"
c) "01310--100"
d) "70040-010"

Ver solução
1
2
3
4
import re

for cep in ["01310-100", "01310100", "01310--100", "70040-010"]:
    print(cep, bool(re.fullmatch(r"\d{5}-?\d{3}", cep)))

Saída:

1
2
3
4
01310-100 True
01310100 True
01310--100 False
70040-010 True

Resposta: c. O ? torna o hífen opcional, ou seja, zero ou um hífen. Dois hífens não casam, e como o fullmatch() exige a string inteira, o valor é rejeitado.

Quer praticar manipulação de texto, arquivos e automações em projetos guiados? Conheça o nosso curso de Python completo.

Conclusão

Neste guia de expressões regulares em Python, você aprendeu:

✅ Funções do re - search, match, fullmatch, findall, finditer, sub e split
✅ Metacaracteres - classes, quantificadores, âncoras e alternativas
✅ Grupos - numerados, nomeados e sem captura
✅ Flags e compile - IGNORECASE, MULTILINE, DOTALL, VERBOSE
✅ Exemplos brasileiros - CPF, CEP, telefone, e-mail e data, com validação real em código

Principais lições:

  • Padrão de regex sempre com r"..."
  • Para validar, fullmatch(); para encontrar, search()
  • Teste o None antes de chamar .group()
  • .*? evita capturas gulosas
  • Regex confere formato, não a existência do dado

Próximos passos:

  • Revise os métodos de strings no Python para saber quando regex não é necessária
  • Use regex para limpar e extrair dados de um arquivo de texto seu
  • Explore o Regular Expression HOWTO para recursos como lookahead

Se ficou com alguma dúvida, fique à vontade para deixar um comentário no box aqui embaixo! Será um prazer te responder! :wink:

Perguntas frequentes

O que são expressões regulares em Python?

Expressões regulares (regex) são padrões de texto usados para buscar, validar, extrair e substituir trechos de strings. Em Python elas ficam no módulo re da biblioteca padrão, sem instalar nada. Exemplo: re.findall(r'\d+', 'a1b22') devolve ['1', '22'], todos os trechos formados por um ou mais dígitos.

Qual a diferença entre re.search, re.match e re.fullmatch?

re.search() procura o padrão em qualquer posição da string. re.match() só aceita o padrão se ele começar na posição 0. re.fullmatch() exige que a string inteira case com o padrão, do primeiro ao último caractere. Para validar formatos (CEP, CPF, data), use fullmatch(); para encontrar algo dentro de um texto, use search().

Por que usar r antes da string da regex?

O prefixo r cria uma raw string, em que a barra invertida não é interpretada pelo Python. Sem ele, '\b' vira o caractere de backspace e o padrão falha em silêncio, e sequências como '\d' geram SyntaxWarning: invalid escape sequence a partir do Python 3.12. Com r'\d', a barra chega intacta ao módulo re.

Qual a diferença entre findall e finditer?

re.findall() devolve uma lista com os textos encontrados (ou tuplas, se o padrão tiver grupos). re.finditer() devolve um iterador de objetos Match, que dão acesso a posição (start(), end()), grupos nomeados e ao texto de cada ocorrência. Use finditer() em textos grandes ou quando precisar mais do que o texto.

Como validar CPF com regex em Python?

Para o formato com pontos e hífen, use re.fullmatch(r'\d{3}\.\d{3}\.\d{3}-\d{2}', cpf). Mas a regex só confere o formato: 111.111.111-11 passa e não é um CPF válido. Para saber se o CPF é válido, calcule também os dois dígitos verificadores com o algoritmo do módulo 11, como mostrado neste post.

O que são grupos nomeados em regex?

Grupos nomeados usam a sintaxe (?P<nome>...) e permitem acessar o trecho capturado pelo nome em vez do número: m.group('ano') ou m['ano']. O método groupdict() devolve todos eles em um dicionário. Na substituição com re.sub(), o grupo é referenciado como \g<nome>.

O que retorna re.match(r’\d+’, ‘abc123’)?

Retorna None. O re.match() só procura no início da string, e 'abc123' começa com letras. Para achar o 123, use re.search(r'\d+', 'abc123'), que devolve um objeto Match com group() igual a '123'.

Quando não usar expressões regulares?

Evite regex quando um método de string resolve: in, startswith(), endswith(), replace() e split() são mais legíveis e rápidos para buscas fixas. Também não use regex para interpretar HTML, JSON ou XML completos; prefira bibliotecas próprias, como BeautifulSoup e o módulo json. E lembre que regex valida formato, não a existência real de um dado.

Começe agora sua Jornada na Programação!

Não deixe para amanhã o sucesso que você pode começar a construir hoje!

#newsletter Olá :wave: Curtiu o artigo? Então faça parte da nossa Newsletter! Privacidade Não se preocupe, respeitamos sua privacidade. Você pode se descadastrar a qualquer momento.