Python Expressões Regulares (re)
Os métodos de texto da aula Métodos de Texto procuram um texto exato: "pão" in recado, recado.replace("pão", "bolo"). Mas e para achar "qualquer telefone", "qualquer CPF", "qualquer email" num texto? Você não sabe qual número procurar, só o formato dele. Para isso existem as expressões regulares (em inglês, regular expressions, ou regex): uma mini-linguagem para descrever padrões, como "dois dígitos entre parênteses, um espaço, quatro ou cinco dígitos, um hífen, quatro dígitos". Em Python, elas moram no módulo re.
- A procurar com
re.searche a ler o resultado (o objetoMatch, ouNone). - Os símbolos principais:
\d,\w,\s,.,[ ],+,*,?,{n},^,$,\b. - Por que todo padrão se escreve com
r"…", e o que acontece quando você esquece. - A achar todos com
findall, trocar comsub, quebrar comsplite validar comfullmatch. - Grupos
( ), grupos com nome e como eles mudam ofindall. - Padrões de CPF, CEP, telefone e email brasileiros, e os limites deles.
- Os erros reais:
SyntaxWarning: invalid escape sequence,'NoneType' object has no attribute 'group',re.PatternError, o ponto que aceita tudo e o.*guloso.
A ideia: o molde de procurar
A Ana tem uma pilha de recados e quer anotar todos os telefones. Ela não lê cada palavra perguntando "isto é o 3222-1080?": ela bate o olho procurando o formato "quatro algarismos, hífen, quatro algarismos". Uma expressão regular é esse formato escrito de um jeito que o computador entende:
| O que a Ana pensa | Na regex |
|---|---|
| um algarismo | \d |
| quatro algarismos | \d{4} |
| um hífen | - |
| quatro algarismos, hífen, quatro algarismos | \d{4}-\d{4} |
Cada letra ou símbolo comum (-, a, @) quer dizer ele mesmo. Alguns símbolos têm um significado especial, como \d ("um dígito") e {4} ("quatro vezes o anterior"). O resto da aula é aprender esses símbolos e as funções do re que usam o padrão.
1. A primeira busca: re.search
import re recado = "Ana, o pedido 4521 sai às 15h. Ligue para 3222-1080." achado = re.search(r"\d+", recado) print(achado) print(achado.group()) print(achado.start(), achado.end()) print(re.search(r"\d+", "sem números aqui"))
$ python3 primeiro.py <re.Match object; span=(14, 18), match='4521'> 4521 14 18 None
- import re
- O módulo das expressões regulares. Ele vem com o Python, não precisa instalar.
- r"\d+"
- O padrão.
\dé "um dígito" (de 0 a 9) e+é "um ou mais do anterior": uma sequência de dígitos. Orna frente das aspas é obrigatório na prática; a seção 3 explica. - re.search(padrao, texto)
- "Procurar": percorre o texto e para no primeiro trecho que casa com o padrão.
- <re.Match object; span=(14, 18), match='4521'>
- O resultado é um objeto
Match("casamento"): achou'4521', do índice 14 até antes do 18 (como nas fatias da aula Índices e Fatias). - achado.group()
- O texto encontrado.
- None
- Quando não acha nada,
searchdevolveNone(aula O Valor None). Por isso, antes de usar o resultado, confira comif achado:.
2. Os símbolos
Estes são os símbolos que você vai usar quase sempre. Não é preciso decorar: volte a esta tabela quando precisar.
| Símbolo | Casa com | Exemplo | Casa em… |
|---|---|---|---|
\d | um dígito, 0 a 9 | \d\d | 86 |
\w | uma "letra de palavra": letra (com acento também), dígito ou _ | \w+ | pão, ana_1 |
\s | um espaço em branco (espaço, tab, quebra de linha) | a\sb | a b |
. | qualquer caractere (menos a quebra de linha) | p.o | pão, pio, p-o |
[abc] | um dos caracteres da lista | [,;] | , ou ; |
[0-9], [A-Z] | um caractere da faixa | [A-Z] | uma maiúscula sem acento |
[^abc] | um caractere que não está na lista | [^0-9] | tudo menos dígito |
\., \(, \- | o próprio ., (… (a barra tira o poder especial) | \d\.\d | 3.5 |
| Quantas vezes (valem para o que vem logo antes) | |||
+ | uma ou mais | \d+ | 7, 4521 |
* | zero ou mais | \d* | nada, 12 |
? | zero ou uma (opcional) | 9?\d{4} | 3222, 98765 |
{n} | exatamente n | \d{3} | 123 |
{n,m} | de n a m | \d{4,5} | 3222, 99876 |
| Posições (não consomem caracteres) | |||
^ | começo do texto | ^Ana | "Ana" só no começo |
$ | fim do texto | \.$ | um ponto no fim |
\b | borda de palavra (entre \w e não-\w) | \bpão\b | "pão", mas não "pãozinho" |
| Outros | |||
a|b | a ou b | pão|bolo | pão, bolo |
( ) | grupo: junta e "captura" um pedaço (seção 5) | (\d{2}) | o DDD |
3. Por que r"…"
As expressões regulares usam muito a barra invertida \. Só que as strings do Python também usam a barra para os caracteres especiais (aula Caracteres de Escape): "\n" é uma quebra de linha, "\t" um tab, "\b" um caractere de "voltar uma casa" (backspace). Quando você escreve "\bpão\b", o Python transforma os \b em backspaces antes de o re ver o padrão.
O r na frente das aspas faz uma string crua (raw string): nela, a barra é só uma barra, e chega intacta ao re.
import re
texto = "o pão da padaria, o pãozinho e o pão de queijo"
print(len("\b"), len(r"\b"))
print(re.findall("\bpão\b", texto))
print(re.findall(r"\bpão\b", texto))
$ python3 cru.py 1 2 [] ['pão', 'pão']
len("\b")é 1: um caractere só, o backspace.len(r"\b")é 2: a barra e ob, que oreentende como "borda de palavra".- Sem o
r, a busca procurou "backspace, pão, backspace", que não existe no texto: lista vazia, sem erro nenhum. - Com o
r, achou os dois "pão" soltos e deixou de fora o "pãozinho" (depois do "pão" dele vem umz, que é letra: não há borda ali).
Com \d, a falta do r não muda o resultado, porque \d não é um escape do Python. Mas o Python avisa, e o aviso aparece toda vez que o programa roda:
import re
print(re.findall("\d+", "pedido 4521"))
$ python3 aviso.py
/home/ana/aviso.py:3: SyntaxWarning: "\d" is an invalid escape sequence. Such sequences will not work in the future. Did you mean "\\d"? A raw string is also an option.
print(re.findall("\d+", "pedido 4521"))
['4521']
"Aviso de sintaxe: \d é uma sequência de escape inválida. Sequências assim não vão funcionar no futuro. Você quis dizer \\d? Uma string crua também é uma opção." É um aviso, e não um erro: o programa rodou e mostrou ['4521'] na última linha. A linha de cima mostra onde está o problema: arquivo aviso.py, linha 3. O editor do guia mostra o mesmo aviso. A regra é simples: todo padrão de regex com r"…", sempre, e nunca mais pense nisso.
4. findall: todos os trechos
re.findall(padrao, texto) ("achar todos") devolve uma lista com todos os trechos que casam, na ordem em que aparecem:
import re
recado = "Ana, o pedido 4521 sai às 15h. Ligue para 3222-1080."
print(re.findall(r"\d+", recado))
print(re.findall(r"\d{4}", recado))
print(re.findall(r"\d{4}-\d{4}", recado))
print(re.findall(r"[A-Z]\w*", recado))
print(re.findall(r"\bp\w+", recado))
$ python3 findall.py ['4521', '15', '3222', '1080'] ['4521', '3222', '1080'] ['3222-1080'] ['Ana', 'Ligue'] ['pedido', 'para']
- \d+
- Toda sequência de dígitos: até o
15de "15h" e as duas metades do telefone, separadas. - \d{4}
- Exatamente quatro dígitos seguidos. O
15ficou de fora. - \d{4}-\d{4}
- O formato do telefone: só o
3222-1080. - [A-Z]\w*
- Uma maiúscula seguida de zero ou mais letras: as palavras que começam com maiúscula.
- \bp\w+
- Uma borda de palavra, um
pe mais letras: palavras que começam com p. Sem o\b, umpno meio da palavra também serviria: em"a compra do pão",r"p\w+"acha['pra', 'pão'], er"\bp\w+"acha só['pão'].
Quando não acha nada, findall devolve uma lista vazia, [], sem erro.
Missão: tire da mensagem os dois telefones com DDD. Use re.findall com o padrão: parêntese, dois dígitos, parêntese, espaço, 4 ou 5 dígitos, hífen, 4 dígitos. Lembre que o parêntese é especial: escreva \( e \).
import re mensagem = "Encomendas: (86) 3222-1080 ou (86) 99876-5432. Pedido 4521." print(mensagem)
import re
mensagem = "Encomendas: (86) 3222-1080 ou (86) 99876-5432. Pedido 4521."
print(re.findall(r"\(\d{2}\) \d{4,5}-\d{4}", mensagem))
5. Grupos: pegar só um pedaço
Às vezes você quer o telefone inteiro para achar, mas só o DDD para guardar. Parênteses ( ) no padrão formam um grupo: o re lembra o que casou com cada grupo, e você pega pelo número.
import re
contato = "Ana: (86) 99876-5432"
achado = re.search(r"\((\d{2})\) (\d{4,5})-(\d{4})", contato)
print(achado.group())
print(achado.group(1))
print(achado.group(2), achado.group(3))
print(achado.groups())
achado = re.search(r"\((?P<ddd>\d{2})\) (?P<numero>\d{4,5}-\d{4})", contato)
print(achado["ddd"], achado["numero"])
print(achado.groupdict())
$ python3 grupos.py
(86) 99876-5432
86
99876 5432
('86', '99876', '5432')
86 99876-5432
{'ddd': '86', 'numero': '99876-5432'}
- \( … \)
- Os parênteses de verdade do telefone, com barra: casam com o caractere
(e). - (\d{2})
- Parênteses sem barra: um grupo. O primeiro grupo guarda o DDD.
- group() / group(1) / group(2)
- Sem número (ou
group(0)), o trecho inteiro. Com número, só o grupo: contam-se os(da esquerda para a direita. - groups()
- Todos os grupos, numa tupla.
- (?P<ddd>…)
- Um grupo com nome. Fica mais fácil de ler que "grupo 1":
achado["ddd"]. Ogroupdict()devolve um dicionário com todos.
Grupos mudam o que o findall devolve
Atenção a este detalhe, que pega muita gente: se o padrão tem grupos, o findall devolve só os grupos, e não o trecho inteiro.
import re
agenda = "Ana (86) 99876-5432, Bruno (11) 3456-7890, Carla (21) 98888-1111"
print(re.findall(r"\((\d{2})\) [\d-]+", agenda))
print(re.findall(r"(\w+) \((\d{2})\)", agenda))
$ python3 findall_grupos.py
['86', '11', '21']
[('Ana', '86'), ('Bruno', '11'), ('Carla', '21')]
Com um grupo, vem uma lista de textos (só os DDDs). Com dois ou mais, uma lista de tuplas, uma por trecho encontrado. Se você precisa de parênteses só para juntar (para pôr um ? num pedaço inteiro, por exemplo) sem mudar o findall, use o grupo que não captura: (?:…). Ele aparece na seção 7.
6. sub: procurar e trocar
re.sub(padrao, troca, texto) ("substituir") troca cada trecho que casa pelo texto de troca. Na troca, \1, \2… são os grupos encontrados:
import re
cadastro = "Cliente: Ana Souza, CPF 123.456.789-09, nascida em 12/03/1985"
print(re.sub(r"\d{3}\.\d{3}\.\d{3}-\d{2}", "***.***.***-**", cadastro))
print(re.sub(r"(\d{2})/(\d{2})/(\d{4})", r"\3-\2-\1", cadastro))
print(re.sub(r"\s+", " ", "pão de queijo"))
cpf_cru = "12345678909"
print(re.sub(r"(\d{3})(\d{3})(\d{3})(\d{2})", r"\1.\2.\3-\4", cpf_cru))
$ python3 sub.py Cliente: Ana Souza, CPF ***.***.***-**, nascida em 12/03/1985 Cliente: Ana Souza, CPF 123.456.789-09, nascida em 1985-03-12 pão de queijo 123.456.789-09
- Esconder o CPF: o padrão casa com o formato do CPF e a troca é um texto fixo.
- Inverter a data: três grupos (dia, mês, ano) e a troca
r"\3-\2-\1"os escreve na ordem ano-mês-dia. A troca também levar, por causa das barras. - Espaços repetidos:
\s+(um ou mais espaços) vira um espaço só. - Formatar o CPF: 11 dígitos colados, separados em quatro grupos e remontados com pontos e hífen.
Missão: a ficha vai ser impressa, e os CPFs não podem aparecer inteiros: deixe visíveis só os dois últimos dígitos. Use re.sub com um grupo nos dois dígitos do fim, -(\d{2}), e a troca r"***.***.***-\1".
import re ficha = "Ana Souza, CPF 123.456.789-09; Bruno Lima, CPF 987.654.321-00" print(ficha)
import re
ficha = "Ana Souza, CPF 123.456.789-09; Bruno Lima, CPF 987.654.321-00"
print(re.sub(r"\d{3}\.\d{3}\.\d{3}-(\d{2})", r"***.***.***-\1", ficha))
split: quebrar em vários separadores
O texto.split(",") quebra só numa vírgula exata. O re.split quebra onde o padrão casar, então aceita vários separadores e espaços variados de uma vez:
import re lista = "pão, leite;café ; manteiga,açúcar" print(re.split(r"\s*[,;]\s*", lista))
$ python3 split.py ['pão', 'leite', 'café', 'manteiga', 'açúcar']
7. Padrões brasileiros: CPF, CEP, telefone e email
Para validar um campo (o texto inteiro tem de estar no formato, nada antes nem depois), use re.fullmatch ("casamento completo"):
import re
CPF = r"\d{3}\.\d{3}\.\d{3}-\d{2}"
CEP = r"\d{5}-?\d{3}"
EMAIL = r"[\w.+-]+@[\w-]+(\.[\w-]+)+"
testes = [
(CPF, "123.456.789-09"),
(CPF, "123.456.789-0"),
(CPF, "CPF 123.456.789-09"),
(CEP, "64000-120"),
(CEP, "64000120"),
(EMAIL, "ana.souza@email.com.br"),
(EMAIL, "ana@email"),
(EMAIL, "ana souza@email.com"),
]
for padrao, texto in testes:
ok = re.fullmatch(padrao, texto)
print(f"{texto!r:26} {'válido' if ok else 'inválido'}")
$ python3 validar.py '123.456.789-09' válido '123.456.789-0' inválido 'CPF 123.456.789-09' inválido '64000-120' válido '64000120' válido 'ana.souza@email.com.br' válido 'ana@email' inválido 'ana souza@email.com' inválido
| Padrão | Lendo em português |
|---|---|
\d{3}\.\d{3}\.\d{3}-\d{2} | CPF: 3 dígitos, ponto, 3 dígitos, ponto, 3 dígitos, hífen, 2 dígitos. Os pontos com barra: são pontos de verdade. |
\d{5}-?\d{3} | CEP: 5 dígitos, hífen opcional (?), 3 dígitos. |
[\w.+-]+@[\w-]+(\.[\w-]+)+ | Email (simplificado): letras, pontos, + ou -; uma arroba; o nome do domínio; e um ou mais pedaços "ponto + nome" (.com, .com.br). |
A regex confere a forma, não a verdade. 111.111.111-11 passa no padrão de CPF, mas não é um CPF válido: os dois últimos dígitos de um CPF são calculados a partir dos nove primeiros (os dígitos verificadores), e essa conta se faz com Python comum, não com regex. Do mesmo jeito, um email no formato certo pode não existir. O padrão de email acima é simplificado de propósito: o padrão "completo" da internet é enorme, e para o dia a dia este basta.
search, match ou fullmatch?
import re
texto = "CPF 123.456.789-09"
cpf = r"\d{3}\.\d{3}\.\d{3}-\d{2}"
print("search: ", re.search(cpf, texto))
print("match: ", re.match(cpf, texto))
print("fullmatch:", re.fullmatch(cpf, texto))
print("fullmatch:", re.fullmatch(cpf, "123.456.789-09"))
$ python3 match_search.py search: <re.Match object; span=(4, 18), match='123.456.789-09'> match: None fullmatch: None fullmatch: <re.Match object; span=(0, 14), match='123.456.789-09'>
| Função | Procura onde | Use para |
|---|---|---|
re.search | Em qualquer lugar do texto (o primeiro) | "Tem um CPF neste texto?" |
re.match | Só no começo do texto | Raramente; search com ^ diz o mesmo e fica mais claro |
re.fullmatch | O texto inteiro | Validar um campo de formulário |
re.findall | Todos os trechos | Tirar todos os telefones de uma página |
Telefone: uma regex é sempre um acordo
Telefones brasileiros aparecem de muitos jeitos: com ou sem DDD, com ou sem parênteses, fixo (8 dígitos) ou celular (9 dígitos, começando com 9). Uma primeira tentativa:
import re
agenda = """Padaria: (86) 3222-1080
Ana: (86) 99876-5432
Bruno: 86 98765-4321
Carla: 99123-4567
Pedido número 1234-5678-90"""
padrao = r"\(?\d{2}\)? ?9?\d{4}-\d{4}"
for numero in re.findall(padrao, agenda):
print(numero)
$ python3 telefones.py (86) 3222-1080 (86) 99876-5432 86 98765-4321
O padrão: parêntese opcional \(?, DDD \d{2}, parêntese opcional \)?, espaço opcional ?, um 9 opcional 9?, 4 dígitos, hífen, 4 dígitos. Achou três, mas perdeu a Carla, que não tem DDD. Para tornar o DDD inteiro opcional, é preciso juntá-lo num grupo e pôr um ? no grupo. Como o grupo não deve mudar o findall, usamos (?:…), o grupo que não captura. E, para não pegar pedaços de números maiores, bordas \b e um (?!-), "e não pode vir hífen depois":
import re
agenda = """Padaria: (86) 3222-1080
Ana: (86) 99876-5432
Bruno: 86 98765-4321
Carla: 99123-4567
Pedido número 1234-5678-90"""
padrao = r"(?:\(?\d{2}\)? ?)?\b9?\d{4}-\d{4}\b(?!-)"
for numero in re.findall(padrao, agenda):
print(numero)
$ python3 telefones3.py (86) 3222-1080 (86) 99876-5432 86 98765-4321 99123-4567
Agora vêm os quatro, e o número do pedido ficou de fora. Sem o (?!-), o 1234-5678 do pedido entrava como se fosse telefone. Essa é a lição de toda regex: quanto mais formatos ela aceita, mais chance de pegar o que não devia. Teste sempre com exemplos que devem casar e com exemplos que não devem.
8. Opções e padrões reaproveitados
import re
texto = "PÃO francês, Pão doce, pão de queijo"
print(re.findall(r"pão", texto))
print(re.findall(r"pão", texto, re.IGNORECASE))
cep = re.compile(r"\d{5}-\d{3}")
for endereco in ["Rua A, 64000-120", "Rua B, sem CEP", "Av. C, 01310-100"]:
achado = cep.search(endereco)
print(achado.group() if achado else "—")
$ python3 flags.py ['pão'] ['PÃO', 'Pão', 'pão'] 64000-120 — 01310-100
re.IGNORECASE("ignorar maiúsculas"): a busca não diferenciaPÃO,Pãoepão, com acento e tudo.re.compile(padrao)prepara o padrão uma vez e devolve um objeto com os mesmos métodos (search,findall,sub…). Deixa o laço mais limpo e dá um nome ao padrão.achado.group() if achado else "—": oifem uma linha (aula if em uma Linha) protege doNone.
Formas diferentes: regex ou método de texto?
| Tarefa | Sem regex | Com regex | Qual usar |
|---|---|---|---|
| Tem a palavra "pão"? | "pão" in texto | re.search(r"pão", texto) | O in: mais simples e mais rápido |
| Trocar um texto fixo | texto.replace("a", "b") | re.sub(r"a", "b", texto) | O replace |
| Quebrar numa vírgula | texto.split(",") | re.split(r",", texto) | O split |
| Só dígitos? | texto.isdigit() | re.fullmatch(r"\d+", texto) | Os dois servem |
| Achar todos os telefones, CPFs, datas | Laço complicado | re.findall | Regex |
| Validar um formato | Vários if | re.fullmatch | Regex |
| Quebrar em vários separadores | replace e split em sequência | re.split(r"[,;]", texto) | Regex |
Regras de bolso: se dá para fazer com um método de texto, faça com ele, porque fica mais fácil de ler. Use regex quando o que você procura é um formato.
Quando dá errado
r
É a seção 3: com "\d", o Python avisa com SyntaxWarning: "\d" is an invalid escape sequence, e o programa funciona. Com "\b", não há aviso nenhum, e a busca não acha nada, porque "\b" virou um backspace. Como consertar: r"…" em todo padrão.
import re
contato = "Bruno: sem telefone"
achado = re.search(r"\d{4}-\d{4}", contato)
print(achado.group())
$ python3 none_group.py
Traceback (most recent call last):
File "/home/ana/none_group.py", line 5, in <module>
print(achado.group())
^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'group'
"O objeto NoneType não tem o atributo group". O search não achou telefone e devolveu None; na linha 5, o programa pediu .group() ao None. É o erro mais comum com regex. Como consertar: if achado: print(achado.group()), com um else para o caso de não achar.
import re
print(re.findall(r"(\d{2}", "(86) 3222-1080"))
$ python3 parenteses.py
Traceback (most recent call last):
File "/home/ana/parenteses.py", line 3, in <module>
print(re.findall(r"(\d{2}", "(86) 3222-1080"))
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/re/__init__.py", line 278, in findall
return _compile(pattern, flags).findall(string)
~~~~~~~~^^^^^^^^^^^^^^^^
«… mais 14 linhas de dentro do módulo re …»
re.PatternError: missing ), unterminated subpattern at position 0
O traceback é longo porque o erro foi descoberto lá dentro do módulo re, enquanto ele lia o padrão. Leia a primeira linha que é do seu arquivo (a linha 3) e a última: re.PatternError ("erro de padrão"): "falta um ), subpadrão não terminado, na posição 0". A posição 0 é o ( que abriu e não fechou. A intenção era um parêntese de verdade, que precisa de barra. Como consertar: r"\(\d{2}". (Os caminhos /usr/lib/python3.14/… mudam de computador para computador.)
re.PatternError e re.error
Até o Python 3.12, esse erro se chamava só re.error. Desde o 3.13, o nome é re.PatternError, e re.error continua valendo como apelido. Um except re.error: funciona nos dois.
import re
sem_barra = r"\d{3}.\d{3}.\d{3}-\d{2}"
com_barra = r"\d{3}\.\d{3}\.\d{3}-\d{2}"
print(re.fullmatch(sem_barra, "123a456b789-09"))
print(re.fullmatch(com_barra, "123a456b789-09"))
$ python3 ponto.py <re.Match object; span=(0, 14), match='123a456b789-09'> None
Sem a barra, o . quer dizer "qualquer caractere", e o "CPF" 123a456b789-09 passou como válido. Como consertar: \. sempre que você quer um ponto de verdade. O mesmo vale para ( ) [ ] { } ? * + | ^ $.
.* guloso engoliu demais
import re html = "<b>pão</b> e <b>café</b>" print(re.findall(r"<b>.*</b>", html)) print(re.findall(r"<b>.*?</b>", html)) print(re.findall(r"<b>(.*?)</b>", html))
$ python3 guloso.py ['<b>pão</b> e <b>café</b>'] ['<b>pão</b>', '<b>café</b>'] ['pão', 'café']
O * é guloso: pega o máximo que conseguir. O .* foi do primeiro <b> até o último </b>, num trecho só. Com ? depois (.*?), ele vira preguiçoso: pega o mínimo, e para no primeiro </b>. Com o grupo, vêm só as palavras. (Para ler páginas da internet de verdade, existem bibliotecas próprias; regex serve para trechos simples como este.)
Referência rápida
| Função | Devolve |
|---|---|
re.search(p, texto) | O primeiro Match, ou None |
re.fullmatch(p, texto) | Match se o texto inteiro casa, ou None |
re.match(p, texto) | Match se casa no começo, ou None |
re.findall(p, texto) | Lista com todos os trechos (ou só os grupos, se houver) |
re.sub(p, troca, texto) | Texto novo com as trocas (\1 na troca = grupo 1) |
re.split(p, texto) | Lista dos pedaços entre os trechos que casam |
re.compile(p) | Um padrão pronto, com os mesmos métodos |
m.group(), m.group(1), m["nome"] | O trecho inteiro / o grupo 1 / o grupo com nome |
m.groups(), m.groupdict(), m.start(), m.end() | Tupla dos grupos / dicionário dos nomeados / posições |
re.IGNORECASE | Opção: ignora maiúsculas e minúsculas |
| Sintoma | Causa provável |
|---|---|
SyntaxWarning: "\d" is an invalid escape sequence | Padrão sem r |
Busca com \b não acha nada, sem erro | Padrão sem r: "\b" virou backspace |
AttributeError: 'NoneType' object has no attribute 'group' | Não achou: confira if achado: antes |
re.PatternError: missing ), unterminated subpattern | Parêntese aberto sem fechar; para o caractere, use \( |
| Aceitou texto que não devia | . sem barra; search em vez de fullmatch |
findall devolveu só pedaços, ou tuplas | O padrão tem grupos: use (?:…) para juntar sem capturar |
| Pegou um trecho grande demais | .* guloso: use .*? |
Exercícios
1. Três dígitos
Escreva o padrão (sem o r e sem as aspas) que casa com exatamente três dígitos seguidos:
2. O CEP
Complete o padrão do CEP com hífen opcional:
3. Validar o campo
Qual função confere se o texto inteiro está no formato? Escreva só o nome, sem re..
4. Por que escrever r"\bpão\b" e não "\bpão\b"?
5. re.findall(r"(\d{2})-\d{4}", "11-2222 e 33-4444") devolve:
6. re.search(r"\d+", "sem números") devolve:
Missão final: os emails do cadastro
Missão: o programa marca todos os emails como válidos, porque só confere se há uma arroba ou um ponto. Troque o teste "@" in email or "." in email por re.fullmatch(EMAIL, email), usando o padrão que já está pronto. O carla@ e o davi email.com devem sair como inválidos.
import re
EMAIL = r"[\w.+-]+@[\w-]+(\.[\w-]+)+"
cadastros = ["ana@padaria.com.br", "bruno.lima@email.com", "carla@", "davi email.com"]
for email in cadastros:
if "@" in email or "." in email:
print(email, "-> válido")
else:
print(email, "-> inválido")
import re
EMAIL = r"[\w.+-]+@[\w-]+(\.[\w-]+)+"
cadastros = ["ana@padaria.com.br", "bruno.lima@email.com", "carla@", "davi email.com"]
for email in cadastros:
if re.fullmatch(EMAIL, email):
print(email, "-> válido")
else:
print(email, "-> inválido")
Com o fullmatch, o carla@ cai porque falta o domínio depois da arroba, e o davi email.com cai porque não tem arroba (e o espaço não está entre os caracteres aceitos). Experimente acrescentar "eva@escola" à lista: sem um .algo no fim, também é inválido.
Resumo
- Uma expressão regular descreve um formato de texto; o módulo
reprocura, troca e valida com ela. - Todo padrão com
r"…": sem or, o\bvira backspace e o\ddáSyntaxWarning. searchacha o primeiro (ouNone),findallacha todos,subtroca,splitquebra,fullmatchvalida o texto inteiro.\d,\w,\s,.,[ ];+,*,?,{n,m};^,$,\b. Para o caractere em si, barra na frente:\.,\(.- Grupos
( )capturam pedaços (group(1),(?P<nome>…)) e mudam ofindall;(?:…)junta sem capturar. - Regex confere formato, não verdade (CPF tem dígito verificador). Teste com exemplos que devem e que não devem casar.