Validar, limpar e
formatar texto
Ontem o texto virou matéria-prima. Hoje ele passa pela portaria: nada entra no sistema sem ser limpo, checado e formatado. E cada validador nasce com teste.
Ontem o texto virou matéria-prima. Hoje ele passa pela portaria: nada entra no sistema sem ser limpo, checado e formatado. E cada validador nasce com teste.
Separar sanitização de validação — limpar o dado é uma etapa; julgá-lo é outra
Usar os métodos de verificação (isdigit, isalpha, isalnum) e conhecer suas armadilhas
Escrever validadores de senha, e-mail e documento que devolvem True/False
Formatar saída com f-string, zfill, ljust/rjust e produzir relatório alinhado
assert.
Não por má-fé: por distração, teclado no celular, copiar-e-colar de planilha, autocompletar. O sistema que confia no que foi digitado quebra em produção.
campo "idade": " 17 anos" campo "telefone": "(11) 9 8765-4321" campo "e-mail": "ANA @escola.com " campo "CEP": "01310 100"
Virem e conversem: qual foi a última vez que um site recusou um dado correto de vocês por causa de formatação? De quem era o erro — do usuário ou do programa?
Tirar o que sobra e padronizar o que dá. Nenhuma decisão é tomada aqui — só limpeza.
" ANA @X.com " → "ana@x.com"Julgar o dado já limpo. A resposta é sempre True ou False — nunca um print.
Devolver ao usuário no formato de exibição, com máscara e alinhamento.
"11987654321" → "(11) 98765-4321"print no meio. Uma função que limpa, decide e imprime não pode ser reaproveitada nem testada. Cada estágio é uma função.
def sanitizar_email(bruto):
"""Remove espaços e padroniza a caixa do e-mail."""
return bruto.strip().replace(" ", "").lower()
def so_digitos(texto):
"""Devolve apenas os algarismos do texto."""
saida = ""
for c in texto:
if c.isdigit():
saida += c
return saida
>>> sanitizar_email(" ANA @Escola.COM ")
'ana@escola.com'
>>> so_digitos("(11) 98765-4321")
'11987654321'
>>> so_digitos("123.456.789-00")
'12345678900'
so_digitos vale mais que vários replace: o replace só remove o que você previu. O laço com isdigit remove tudo que não for número — inclusive o caractere estranho que você nunca imaginou.
| Método | Pergunta que responde | Exemplo verdadeiro |
|---|---|---|
| .isdigit() | só tem algarismos? | "2026" → True |
| .isalpha() | só tem letras? | "Ana" → True |
| .isalnum() | só tem letras e/ou números? | "ABC123" → True |
| .isspace() | só tem espaço em branco? | " " → True |
| .isupper() / .islower() | está todo em maiúscula/minúscula? | "SP" → True |
| .startswith() / .endswith() | começa/termina com este trecho? | "nota.txt".endswith(".txt") |
if — sem == True, que é redundante: if idade.isdigit(): já basta.
| Expressão | Resultado | Por quê |
|---|---|---|
| "25".isdigit() | True | só algarismos |
| "25.5".isdigit() | False | o ponto não é algarismo |
| "-3".isdigit() | False | o sinal não é algarismo |
| " 25".isdigit() | False | o espaço não é algarismo |
| "".isdigit() | False | string vazia nunca passa |
| "ana maria".isalpha() | False | o espaço não é letra |
isalpha derruba muito validador de nome: qualquer nome composto tem espaço e reprova. O jeito certo é validar palavra por palavra: all(p.isalpha() for p in nome.split()) — ou percorrer com laço, checando cada parte.
" 25 ".strip().isdigit() dá True; sem o strip, dá False e o usuário leva um "idade inválida" injusto.
"01310100".isdigit()"Python3".isalnum()"Ana Maria".isalpha() ✓" ".isspace()isalpha() exige que todos os caracteres sejam letras — e o espaço entre "Ana" e "Maria" não é. A alternativa B passa porque letras e números juntos satisfazem o isalnum (ele só recusaria pontuação ou espaço).
def senha_forte(senha):
"""
Verifica se a senha atende à política mínima.
Args:
senha (str): senha digitada pelo usuário.
Returns:
bool: True se tem 8+ caracteres, ao menos
uma maiúscula, uma minúscula e um dígito.
"""
if len(senha) < 8:
return False
tem_maiuscula = False
tem_minuscula = False
tem_digito = False
for c in senha:
if c.isupper():
tem_maiuscula = True
elif c.islower():
tem_minuscula = True
elif c.isdigit():
tem_digito = True
return tem_maiuscula and tem_minuscula and tem_digito
>>> senha_forte("escola")
False (curta demais)
>>> senha_forte("escola2026")
False (sem maiúscula)
>>> senha_forte("Escola2026")
True
return False logo no tamanho evita percorrer a senha à toa. Validador bem escrito recusa cedo o que já dá para recusar.
any(c.isdigit() for c in senha).
def email_valido(email):
"""Aplica as regras mínimas de formato de e-mail."""
email = email.strip().lower()
if email.count("@") != 1:
return False
usuario, dominio = email.split("@")
if len(usuario) == 0 or len(dominio) == 0:
return False
if "." not in dominio:
return False
if " " in email:
return False
return True
"ana@escola.com" → True
"a@@b.com" → False (count != 1)
"@escola.com" → False (usuário vazio)
"ana@escola" → False (domínio sem ponto)
"an a@x.com" → False (espaço no meio)
count("@") != 1 antes do split: é o que garante que o desempacotamento em duas variáveis não vai quebrar. Validar a estrutura antes de confiar nela.
def cpf_formato_ok(cpf):
"""
Verifica o formato do CPF (não os dígitos
verificadores): 11 algarismos, não todos iguais.
"""
numeros = so_digitos(cpf)
if len(numeros) != 11:
return False
if numeros == numeros[0] * 11:
return False
return True
>>> cpf_formato_ok("123.456.789-00")
True
>>> cpf_formato_ok("111.111.111-11")
False
>>> cpf_formato_ok("123456789")
False
so_digitos foi escrita no slide 5 e agora resolve metade do problema. Função pequena e bem nomeada é peça de encaixe — é por isso que a Semana 17 insistiu em return em vez de print.
numeros[0] * 11 monta uma string com o primeiro dígito repetido 11 vezes. Se for igual ao CPF, todos os dígitos são iguais — o caso clássico de teste que passa no formato mas é inválido de fato.
cpf_formato_ok, não cpf_valido. Validar de verdade exige o cálculo dos dígitos verificadores — bom desafio bônus para quem terminar cedo.
def idade_valida(texto):
if texto.isdigit():
print("idade ok")
else:
print("idade inválida")
if idade_valida(" 17 "):
cadastrar()
int antes do ifreturn, e o dado não foi sanitizado ✓isdigit() não existe para stringsreturn, a função devolve None — o if nunca é verdadeiro e o cadastro nunca acontece, sem erro nenhum na tela. (2) " 17 ".isdigit() é False por causa dos espaços: faltou .strip(). Erro de lógica silencioso — exatamente o tipo que a Semana 18 ensinou a caçar.
O sistema guarda "11987654321" e 1234.5. Ninguém quer ler isso. A última etapa da esteira é devolver o dado no formato que o ser humano espera.
Armazenado 11987654321 1234.5 7 2026-09-11
Exibido (11) 98765-4321 R$ 1.234,50 Nota 07 11/09/2026
Pense: por que não guardar já formatado? (Dica: e se o sistema precisar somar os valores? E se o cliente for de outro país?)
linhas = [
"Ana Silva;8.5;9.0;7.5",
"Bruno Costa;6.0;7.5;8.0",
"Carla Dias;9.5;9.0;10.0",
]
for linha in linhas:
campos = linha.split(";")
nome = campos[0]
notas = [float(n) for n in campos[1:]]
media = sum(notas) / len(notas)
print(f"{nome:<15} média {media:.2f}")
Ana Silva média 8.33 Bruno Costa média 7.17 Carla Dias média 9.50
split + índice resolve.
campos[1:] — fatiamento do Dia 1 — pega "todas as notas, seja quantas forem". Se você escrevesse campos[1], campos[2], campos[3], o código quebraria no dia em que entrasse a quarta nota.
| Recurso | Uso | Resultado |
|---|---|---|
| {v:.2f} | casas decimais | 8.333… → "8.33" |
| {v:<12} | alinha à esquerda em 12 colunas | "Ana " |
| {v:>8} | alinha à direita em 8 colunas | " 8.33" |
| {v:^10} | centraliza em 10 colunas | " Ana " |
| .zfill(3) | completa com zeros à esquerda | "7" → "007" |
| .ljust(n) / .rjust(n) | versão em método do alinhamento | "ok".rjust(5) → " ok" |
| {v:>8.2f} | combina largura e decimais | " 8.33" |
def formatar_telefone(numero):
"""
Aplica a máscara (DD) 9NNNN-NNNN.
Args:
numero (str): telefone com ou sem pontuação.
Returns:
str: telefone mascarado, ou o valor limpo
se não tiver 11 dígitos.
"""
d = so_digitos(numero)
if len(d) != 11:
return d
return f"({d[:2]}) {d[2:7]}-{d[7:]}"
>>> formatar_telefone("11987654321")
'(11) 98765-4321'
>>> formatar_telefone("(11) 9 8765 4321")
'(11) 98765-4321'
>>> formatar_telefone("1198765")
'1198765'
so_digitos), fatiamento (d[2:7]) e f-string. Os três blocos do dia trabalhando juntos.
alunos = [("Ana Silva", 8.33), ("Bruno Costa", 7.17),
("Carla Dias", 9.5)]
print("=" * 34)
print(f"{'ALUNO':<20}{'MÉDIA':>8}{'SIT':>6}")
print("=" * 34)
for nome, media in alunos:
situacao = "APR" if media >= 6 else "REC"
print(f"{nome:<20}{media:>8.2f}{situacao:>6}")
print("-" * 34)
print(f"{'TOTAL':<20}{len(alunos):>8}")
================================== ALUNO MÉDIA SIT ================================== Ana Silva 8.33 APR Bruno Costa 7.17 APR Carla Dias 9.50 APR ---------------------------------- TOTAL 3
"=" * 34 é a repetição de string do Dia 1 fazendo o trabalho de uma linha divisória.
A variável é nota = "7" (texto) e a saída precisa ter dois algarismos.
print("Nota " + nota * 2)print(f"Nota {nota.zfill(2)}") ✓print(f"Nota {nota:>2}")print("Nota " + nota.strip(2))zfill(2) completa com zeros à esquerda até o tamanho 2. A alternativa A repete o texto e sai "Nota 77"; a C completa com espaço e sai "Nota 7" — parece certo no slide e erra no relatório; a D nem roda: strip espera texto, não número.
def testar_email_valido():
# Arrange — casos e gabaritos escritos na mão
casos = [
("ana@escola.com", True), # caminho feliz
(" ANA@Escola.com ", True), # sanitização
("ana@escola", False), # sem ponto
("@escola.com", False), # sem usuário
("a@@b.com", False), # dois arrobas
("", False), # borda: vazio
]
for entrada, esperado in casos:
# Act
obtido = email_valido(entrada)
# Assert
assert obtido == esperado, \
f"{entrada!r}: esperado {esperado}, veio {obtido}"
print("OK: email_valido")
testar_email_valido()
OK: email_valido
assert diz exatamente qual entrada falhou.
email_valido de propósito e rode. Se nenhum caso reclamar, seu teste não está testando o que você pensa (Semana 18, slide 20).
Função com print em vez de return devolve None.
if nunca entra e nada dá erro.
" 25 ".isdigit() é False.
texto.strip() sem atribuir a ninguém.
O espaço reprova qualquer nome com sobrenome.
Sintoma: "Ana Silva" é inválida.Desempacotar em duas variáveis o que veio com três partes.
Sintoma:ValueError em produção.
texto[0] em string vazia.
IndexError quando o campo vem em branco.
Sanitizar — normalizar_nome, sanitizar_email, so_digitos.
Validar — nome_valido (2+ palavras, só letras em cada uma), email_valido, telefone_valido (11 dígitos), senha_forte.
Formatar — formatar_telefone e um relatório final alinhado com os cadastros aceitos.
print. Elas devolvem True/False; quem fala com o usuário é o programa principal.
Args · Returns)assertcpf_formato_ok em cpf_validoisdigit, isalpha, isalnum devolvem True/False e entram direto no ifbool, nunca imprimezfill · máscara por fatiamentoCom texto limpo, validado e formatado, os dados finalmente estão prontos para sair da memória — a próxima etapa é a persistência: gravar e ler arquivos.
.strip().lower() sempre vem primeiroTrue/False — quem imprime é quem chamouif len(x) < n: return False no topoisdigit reprova ponto, sinal, espaço e vazio — e está certo em reprovarsplit()split, confira o count do separadorassert| Semana | O que ficou | O que virou insumo hoje |
|---|---|---|
| 15 · Vetores | sequência indexada, percorrer com laço | o split devolve exatamente isso |
| 17 · Funções | parâmetro, return, reuso | cada etapa da esteira é uma função |
| 18 · Doc e testes | docstring, AAA, assert, depuração | a tabela de casos do validador |
| 19 · Strings D1 | índice, fatia, métodos | a máscara e a limpeza |
| 19 · Strings D2 | sanitizar, validar, formatar | a porta de entrada do sistema |
SEDUC-SP. Educação Profissional Paulista — Técnico em Desenvolvimento de Sistemas. Lógica de Programação — Manipulação de strings (Semana 19, aulas 3 e 4). Códigos: SISANO1C1B3S19A3 e A4.
ASCENCIO, A. F. G.; CAMPOS, E. A. V. Fundamentos da programação de computadores. São Paulo: Pearson Education do Brasil, 2012.
MARTIN, R. C. Código limpo: habilidades práticas do Agile Software. Rio de Janeiro: Alta Books, 2009.
MENEZES, N. N. C. Introdução à programação com Python. 3. ed. São Paulo: Novatec, 2019.
PYTHON SOFTWARE FOUNDATION. Format String Syntax e String Methods. Documentação oficial. Disponível em: docs.python.org. Acesso em: 14 ago. 2026.
Aula compilada (dobradinha) · adaptação para apresentação HTML por Guilherme Antunes