01 / 25
Lógica de Programação · Semana 19 · Dia 2 (aulas 3 e 4)

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.

ProfessorGuilherme Antunes CursoTécnico em Desenvolvimento de Sistemas ConteúdoManipulação de strings CódigosSISANO1C1B3S19A3 · A4
02 / 25
Objetivos do dia

Ao final, você será capaz de.

01

Separar sanitização de validação — limpar o dado é uma etapa; julgá-lo é outra

02

Usar os métodos de verificação (isdigit, isalpha, isalnum) e conhecer suas armadilhas

03

Escrever validadores de senha, e-mail e documento que devolvem True/False

04

Formatar saída com f-string, zfill, ljust/rjust e produzir relatório alinhado

Onde estamos: Dia 1 → recortar e transformar texto. Hoje → decidir se o texto presta e devolvê-lo apresentável. E, fechando o ciclo da Semana 18, cada função sai daqui com assert.
03 / 25
Parte 1 Ponto de partida

Todo dado de usuário é hostil até prova em contrário

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?

Princípio do dia: um bom sistema corrige o que dá para corrigir sozinho (espaço, caixa, pontuação) e só recusa o que realmente não tem conserto.
04 / 25
Construindo o conceito

A esteira de três estágios

1 · Sanitizar

Tirar o que sobra e padronizar o que dá. Nenhuma decisão é tomada aqui — só limpeza.

" ANA @X.com " → "ana@x.com"

2 · Validar

Julgar o dado já limpo. A resposta é sempre True ou False — nunca um print.

tem "@"? tem "."? tamanho ok?

3 · Formatar

Devolver ao usuário no formato de exibição, com máscara e alinhamento.

"11987654321" → "(11) 98765-4321"
O erro mais comum da turma: misturar os três numa função só, cheia de print no meio. Uma função que limpa, decide e imprime não pode ser reaproveitada nem testada. Cada estágio é uma função.
05 / 25
Estágio 1

Sanitizar é encadear métodos

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'
Por que 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.
Padrão de mercado: guarde no banco o dado limpo (só dígitos) e aplique a máscara só na hora de exibir. Assim a busca sempre encontra, independentemente de como o usuário digitou.
06 / 25
Estágio 2 · ferramentas

Os métodos que devolvem True ou False

MétodoPergunta que respondeExemplo 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")
Todos são perguntas fechadas. Por isso encaixam direto no if — sem == True, que é redundante: if idade.isdigit(): já basta.
07 / 25
Armadilha clássica

"É dígito" não é "é número"

ExpressãoResultadoPor quê
"25".isdigit()Truesó algarismos
"25.5".isdigit()Falseo ponto não é algarismo
"-3".isdigit()Falseo sinal não é algarismo
" 25".isdigit()Falseo espaço não é algarismo
"".isdigit()Falsestring vazia nunca passa
"ana maria".isalpha()Falseo espaço não é letra
A pegadinha do 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.
Ordem certa: sanitize antes de verificar. " 25 ".strip().isdigit() dá True; sem o strip, dá False e o usuário leva um "idade inválida" injusto.
08 / 25
Pause e responda

Qual delas é False?

A"01310100".isdigit()
B"Python3".isalnum()
C"Ana Maria".isalpha() ✓
D" ".isspace()
Por que C: o 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).
09 / 25
Estágio 2 · aplicação

Validador de senha forte

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
Saída antecipada: o return False logo no tamanho evita percorrer a senha à toa. Validador bem escrito recusa cedo o que já dá para recusar.
Três flags, um laço. Percorrer uma vez marcando o que encontrou é o padrão para "existe ao menos um…". A alternativa idiomática é any(c.isdigit() for c in senha).
10 / 25
Estágio 2 · aplicação

Validador de e-mail — regra a regra

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

Casos de teste que importam

ok

"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.
Honestidade técnica: nenhum validador de formato prova que o e-mail existe. Isso só se resolve enviando uma mensagem de confirmação. O que fazemos aqui é barrar o erro de digitação óbvio.
11 / 25
Estágio 2 · aplicação

Documento: limpar antes de contar

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
Repare no reuso: 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.
O nome da função conta a verdade: 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.
12 / 25
Pause e responda

Onde está o defeito?

def idade_valida(texto):
    if texto.isdigit():
        print("idade ok")
    else:
        print("idade inválida")

if idade_valida("  17  "):
    cadastrar()
AFalta converter o texto para int antes do if
BA função imprime em vez de return, e o dado não foi sanitizado ✓
Cisdigit() não existe para strings
DNada: o código funciona como esperado
Por que B: dois defeitos somados. (1) Sem return, 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.
13 / 25
Parte 2 Formatação · ponto de partida

Dado aprovado ainda precisa ficar apresentável

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?)

14 / 25
Lendo dados estruturados

Uma linha de texto vira um registro

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
É assim que se lê um CSV. Arquivo de planilha exportado é exatamente isso: uma linha de texto por registro, com um separador entre os campos. 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.
15 / 25
Estágio 3 · ferramentas

Alinhar, preencher e arredondar

RecursoUsoResultado
{v:.2f}casas decimais8.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"
Por que alinhamento importa: relatório em coluna torta é relatório que ninguém confere. E, no terminal, alinhar é literalmente contar caracteres — só a fonte monoespaçada garante o resultado.
16 / 25
Estágio 3 · aplicação

Aplicando máscara com fatiamento

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'
Toda a Semana 19 em uma função: sanitização (so_digitos), fatiamento (d[2:7]) e f-string. Os três blocos do dia trabalhando juntos.
Decisão de projeto: quando não dá para mascarar, a função devolve o dado limpo em vez de quebrar. Formatação é cosmética — não deveria derrubar o programa.
17 / 25
Estágio 3 · aplicação

Um relatório que se lê

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
A linha do cabeçalho e a do dado usam as mesmas larguras (20, 8, 6). É isso — e só isso — que mantém as colunas alinhadas. Mudou uma, mude a outra.
"=" * 34 é a repetição de string do Dia 1 fazendo o trabalho de uma linha divisória.
18 / 25
Pause e responda

Qual imprime "Nota 07"?

A variável é nota = "7" (texto) e a saída precisa ter dois algarismos.

Aprint("Nota " + nota * 2)
Bprint(f"Nota {nota.zfill(2)}") ✓
Cprint(f"Nota {nota:>2}")
Dprint("Nota " + nota.strip(2))
Por que B: 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.
19 / 25
Fechando o ciclo da Semana 18

Validador sem teste é promessa

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
Tabela de casos + laço é o padrão mais econômico para validador: um teste, muitos cenários, e a mensagem do assert diz exatamente qual entrada falhou.
Veja o vermelho antes: apague uma regra do 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).
Validador é campeão de casos de borda: vazio, só espaço, separador duplicado, separador nas pontas. Estes quatro deveriam estar em toda suíte de string.
20 / 25
Mapa de armadilhas

Os seis erros que derrubam validador

Esquecer o retorno

Função com print em vez de return devolve None.

Sintoma: o if nunca entra e nada dá erro.

Validar antes de limpar

" 25 ".isdigit() é False.

Sintoma: dado correto recusado.

Perder o resultado

texto.strip() sem atribuir a ninguém.

Sintoma: a limpeza "não funciona".

isalpha em nome composto

O espaço reprova qualquer nome com sobrenome.

Sintoma: "Ana Silva" é inválida.

split sem conferir

Desempacotar em duas variáveis o que veio com três partes.

Sintoma: ValueError em produção.

Índice sem checar tamanho

texto[0] em string vazia.

Sintoma: IndexError quando o campo vem em branco.
Quatro dos seis são silenciosos — não lançam exceção, só entregam o resultado errado. É por isso que a suíte de testes vale mais em validação do que em qualquer outro lugar.
21 / 25
Colocando em prática · 45 minutos · em duplas

Cadastro à prova de bala

Construam a esteira completa

1

Sanitizar — normalizar_nome, sanitizar_email, so_digitos.

2

Validar — nome_valido (2+ palavras, só letras em cada uma), email_valido, telefone_valido (11 dígitos), senha_forte.

3

Formatar — formatar_telefone e um relatório final alinhado com os cadastros aceitos.

Regra: nenhuma função de validação pode conter print. Elas devolvem True/False; quem fala com o usuário é o programa principal.

Entrega

  • Cada função com docstring (Args · Returns)
  • Cada validador com uma tabela de casos testada por assert
  • Ao menos 3 casos de borda cobertos: vazio, só espaço, separador duplicado
  • Mensagem de recusa que diz qual campo reprovou, não um "dados inválidos" genérico
  • Bônus: implementar o cálculo dos dígitos verificadores do CPF e transformar cpf_formato_ok em cpf_valido
22 / 25
Então ficamos assim…

Fechando a Semana 19 ✓

  • Esteira → sanitizar (limpa) · validar (julga) · formatar (exibe) — uma função por etapa
  • Verificação → isdigit, isalpha, isalnum devolvem True/False e entram direto no if
  • Armadilhas → espaço não é letra, ponto não é dígito, string vazia reprova em tudo
  • Validador → recusa cedo, devolve bool, nunca imprime
  • Formatação → f-string com largura e decimais · zfill · máscara por fatiamento
  • Ciclo completo → documentar (S18) · testar (S18) · recortar (S19 D1) · validar (S19 D2)
Próximo passo

Com 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.

23 / 25
Cola de bolso

Validação em oito linhas

  • Limpe antes de julgar: .strip().lower() sempre vem primeiro
  • Validador devolve True/False — quem imprime é quem chamou
  • Recuse cedo: if len(x) < n: return False no topo
  • isdigit reprova ponto, sinal, espaço e vazio — e está certo em reprovar
  • Nome composto: valide palavra por palavra, depois do split()
  • Antes de desempacotar um split, confira o count do separador
  • Guarde limpo (só dígitos), exiba mascarado (fatiamento + f-string)
  • Todo validador nasce com tabela de casos e assert
A pergunta do dia: "esse dado eu conserto sozinho ou eu recuso?" — a resposta separa a sanitização da validação.
24 / 25
Panorama

Onde a Semana 19 se encaixa

SemanaO que ficouO que virou insumo hoje
15 · Vetoressequência indexada, percorrer com laçoo split devolve exatamente isso
17 · Funçõesparâmetro, return, reusocada etapa da esteira é uma função
18 · Doc e testesdocstring, AAA, assert, depuraçãoa tabela de casos do validador
19 · Strings D1índice, fatia, métodosa máscara e a limpeza
19 · Strings D2sanitizar, validar, formatara porta de entrada do sistema
Ninguém aprende string isolada. O que a semana entregou foi a capacidade de transformar o que o usuário digitou em o que o sistema precisa — com garantia de que a transformação funciona.
25 / 25
Referências

De onde veio cada coisa

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