Unidade 9 · o filtro
O filtro da ficha
O caderno já guarda e-mail e CEP. Agora o filtro: o que a ficha aceita. Padrão no módulo re, não dezenas de if.
"@" in email é frágil. . é qualquer caractere; o ponto de verdade é \.. String crua r"...". ^ e $ (ou fullmatch). \w \d + * ?. Grupos, morsa, re.sub, re.IGNORECASE.
@ no meio não basta
Arquivo valida.py. O cliente digita o e-mail da ficha. Primeira ideia: tem arroba?
email = input("e-mail? ").strip()
if "@" in email:
print("ok")
else:
print("inválido")
ana@padaria.com passa. @ sozinho também. Três arrobas também. A pergunta “tem @ no meio” não é a pergunta “parece e-mail”.
Segunda tentativa: usuario, dominio = email.split("@"). Só funciona com um arroba. Dois: ValueError (unidade 5, vírgula extra no caderno — o mesmo contrato quebrado). E ainda: ".com" in dominio aceita com.padaria.foo no meio da string. dominio.endswith(".com") é melhor e ainda deixa passar ana@.com, espaço, ou maluco@.com.com.
Condicional não escala. Cada canto que você tapa abre outro. O que falta é uma linguagem para descrever o formato.
O módulo re
Expressão regular — regex — é um padrão: uma receita do que o texto pode ser. Python traz isso no módulo re, biblioteca padrão (unidade 7: import).
re.search(padrão, texto) devolve um objeto match ou None. No if, match vale True; None vale False. Não chame .group() no None: AttributeError.
import re
email = input("e-mail? ").strip()
if re.search(".+@.+", email):
print("ok")
else:
print("inválido")
+ é “um ou mais do que veio antes”. . é qualquer caractere (exceto quebra de linha). Então .+@.+ lê: alguma coisa, arroba, alguma coisa. Já é melhor que "@" in. Ainda aceita muita coisa — inclusive um ponto que você queria literal no domínio.
Importe re e use search, match, fullmatch ou sub.
import rere.search(r"\d+", "senha 12")Ponto, barra, string crua
Na regex, . não é o ponto de padaria.com. É coringa. maluco@padaria!com passa em .+@.+\.com? Não, se você escapar. Sem escapar, .+@.+.com aceita ana@padariaXcom — o X ocupa o lugar do ponto.
Ponto de verdade: \.. Barra invertida já é especial nas strings do Python (\n é quebra). Se você escrever ".+@.+\.com" sem o r, o interpretador pode comer a barra antes do re ver. Padrão em string crua: r".+@.+\.com" — o \ chega intacto.
if re.search(r".+@.+\.com", email):
print("ok")
Ainda aceita lixo antes e depois: xxx ana@padaria.com yyy passa, porque search acha o trecho no meio. Validar o campo inteiro é outra pergunta.
Em regex, o que . (sem barra) significa?
Do começo ao fim
^ ancora o começo da string. $ ancora o fim. Juntos: o padrão tem de cobrir o texto inteiro.
if re.search(r"^.+@.+\.com$", email):
print("ok")
re.fullmatch já implica as duas âncoras — o padrão é a string toda, sem escrever ^ e $. Os dois caminhos valem; na ficha, fullmatch deixa a intenção explícita.
Espaço nas pontas: .strip() antes, crachá da unidade 1. Regex não substitui o hábito de limpar o que veio do teclado.
Para exigir que o padrão cubra a string inteira num search, o que se usa?
re.search(r".+@.+\.com", "xxx ana@padaria.com yyy") sem ^ e $. Por que passa?
Maiúscula no domínio
O cliente digita Ana@Padaria.COM. Sem flag, \.com no fim recusa. E-mail no mundo real não distingue maiúscula no domínio. Terceiro argumento (ou flags=):
if re.fullmatch(r".+@.+\.com", email, re.IGNORECASE):
print("ok")
Flags se combinam com |: re.IGNORECASE | re.MULTILINE. Nesta unidade, uma já resolve o crachá.
re.fullmatch(r".+@.+\.com", "ana@PADARIA.COM") sem flag. O que acontece?
Classes e quantificadores
.+ no usuário aceita espaço, segundo arroba, qualquer lixo. Mais honesto: \w — letra, dígito ou _ (“caractere de palavra”). \d é dígito. \s é espaço (e tab, e quebra). Os contrários: \W \D \S.
Conjunto na mão: [a-zA-Z0-9_] é quase o \w. [^@] é “tudo menos arroba” — útil quando o usuário pode ter ponto (ana.silva) mas não um segundo @.
Quantos: + um ou mais; * zero ou mais; ? zero ou um. {5} exatamente cinco; {4,5} de quatro a cinco; {3,} três ou mais. Hífen opcional no CEP: -?.
if re.fullmatch(r"\w+@\w+\.com", email, re.IGNORECASE):
print("ok")
Ainda é um e-mail de brinquedo — não valida a internet, não cobre ana.silva@padaria.com.br. Serve para o formato da ficha. Produção usa biblioteca pronta; aqui o ponto é ler o padrão.
Cinco, hífen, três
CEP brasileiro: cinco dígitos, hífen opcional, três dígitos. r"\d{5}-?\d{3}". fullmatch no campo inteiro, senão 01310-100-extra passa no meio.
import re
cep = input("CEP: ").strip()
if re.fullmatch(r"\d{5}-?\d{3}", cep):
print("ok")
else:
print("inválido")
01310100 e 01310-100 passam. 0131-0100 não: a conta dos dígitos quebrou.
Parênteses que guardam
Até agora o match só dizia sim ou não. Parênteses capturam. r"^(\w+)@(\w+)\.com$": m.group(1) é o usuário, m.group(2) o domínio, m.group(0) o match inteiro. Os pares numeram da esquerda para a direita, de 1 em diante.
Operador morsa (unidade 10 aprofunda): if m := re.fullmatch(...): atribui e testa numa linha. Sem ele, você chama fullmatch duas vezes ou guarda antes do if.
import re
cep = input("CEP: ").strip()
if m := re.fullmatch(r"(\d{5})-?(\d{3})", cep):
print(m.group(1), m.group(2))
(?:...) agrupa sem capturar — quando você só quer | ou quantificar, sem ocupar group(1). | é “ou”: padaria\.com|fila\.local.
Numa regex com parênteses, o que é group(1)?
O link do perfil
O recado chega com URL, não com o @. Às vezes https://instagram.com/ana, às vezes http://www.instagram.com/ana/, às vezes só instagram.com/ana. Você quer ana. split("/") quebra em cada variação de protocolo, www, barra no fim — de novo a floresta de if.
import re
url = input("url? ").strip()
m = re.search(
r"(?:https?://)?(?:www\.)?instagram\.com/(\w+)",
url,
re.IGNORECASE,
)
if m:
print("@" + m.group(1))
https?: o s é opcional. (?:www\.)?: o www opcional, grupo sem captura — o group(1) continua sendo o nome. Ponto do domínio escapado: instagram\.com.
Trocar o que bateu
re.sub(padrão, troca, texto) devolve a string já substituída. Se não achar, devolve o texto igual. Tirar hífen do CEP, mascarar dígito, colapsar espaço.
limpo = re.sub(r"\D", "", "01310-100")
print(limpo) # 01310100
Na troca, \1 é o primeiro grupo: re.sub(r"(\d{5})-?(\d{3})", r"\1-\2", cep) normaliza com hífen. String crua na troca também, senão \1 vira outra coisa.
re.MULTILINE faz ^ e $ valerem por linha. re.VERBOSE deixa o padrão quebrado, com comentário ao lado. Entre quando o padrão crescer; o mínimo da unidade é search + âncora + um grupo.
Um trecho com re.sub(...).
re.sub(r"\d", "#", "senha 12")Oficina · validar CEP
re + print. Arquivo: valida.py.
Cole um programa com re e print.
import re
cep = input("CEP: ").strip()
print("ok" if re.fullmatch(r"\d{5}-?\d{3}", cep) else "inválido")