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()efullmatch()devolvemNone. - 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! ![]()
Vá Direto ao Assunto…
- O que são expressões regulares
- As funções do módulo re
- Metacaracteres: a tabela de referência
- Grupos e grupos nomeados
- Flags e padrões compilados
- Exemplos brasileiros: CPF, CEP, telefone, e-mail e data
- Qual função do re usar?
- Erros comuns
- Exercícios resolvidos
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, usefullmatch().
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? ![]()
Que tal receber 30 dias de conteúdo direto na sua Caixa de Entrada?
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
Noneantes 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! ![]()
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.
"Porque o Senhor dá a sabedoria, e da sua boca vem a inteligência e o entendimento" Pv 2:6