Em se tratando de repetição, o Python tem mais recursos do que a maioria das linguagens revela à primeira vista. Vamos percorrer aqui as estruturas de repetição, entender quando cada tipo de loop é a escolha certa e conhecer os padrões que aparecem com mais frequência em processamento de dados e IA.
Antes da sintaxe, uma justificativa. Em Machine Learning e Deep Learning, loops estão em absolutamente tudo: iterar sobre um dataset linha a linha no pré-processamento, executar épocas de treinamento — cada época é uma passagem completa sobre os dados —, percorrer as camadas de uma rede neural, avaliar métricas sobre múltiplos exemplos e testar combinações na busca de hiperparâmetros. Escrever loops eficientes e Pythonicos é, sem exagero, uma das habilidades mais exercitadas no dia a dia de quem trabalha com dados.
O for é o ponto de partida, e vale dizer logo o que ele não é: diferente de C ou Java, o for do Python não é um contador, é um iterador. Ele percorre qualquer objeto iterável — listas, strings, ranges, dicionários, arquivos. Sobre uma lista, a leitura é quase literal:
frutas = ["maçã", "banana", "laranja", "uva"]
for fruta in frutas:
print(fruta)
Saída:
maçã
banana
laranja
uva
Strings também são iteráveis, então percorrer caractere por caractere não exige nada de especial:
palavra = "Python"
for letra in palavra:
print(letra)
Saída:
P
y
t
h
o
n
Quando o que você precisa é um índice ou simplesmente repetir algo N vezes, entra o range(), que gera uma sequência de números. Na forma mais simples, recebe só o limite e conta a partir do zero:
# range(stop) — de 0 até stop-1
for i in range(5):
print(i)
Saída:
0
1
2
3
4
Com dois argumentos, você define onde começa e onde termina:
# range(start, stop) — de start até stop-1
for i in range(2, 7):
print(i)
Saída:
2
3
4
5
6
E com três, controla também o passo:
# range(start, stop, step) — com passo
for i in range(0, 20, 5):
print(i)
Saída:
0
5
10
15
Passo negativo faz a contagem andar para trás, o que resolve contagens regressivas sem nenhuma gambiarra:
# Contagem regressiva com passo negativo
for i in range(10, 0, -1):
print(i)
Saída:
10
9
8
7
6
5
4
3
2
1
Em torno do for existe um conjunto de funções embutidas que tornam os loops bem mais expressivos, e conhecê-las é o que separa código Pythonico de código traduzido de outra linguagem.
A primeira é enumerate(), para quando você precisa do índice e do valor ao mesmo tempo. Ela substitui com vantagem o velho range(len(...)):
# Jeito não-Pythonico (evite)
frutas = ["maçã", "banana", "laranja"]
for i in range(len(frutas)):
print(f"{i}: {frutas[i]}")
# Jeito Pythonico (prefira)
for i, fruta in enumerate(frutas):
print(f"{i}: {fruta}")
Saída de ambos:
0: maçã
1: banana
2: laranja
Se a contagem precisa começar em outro número, basta dizer:
for i, fruta in enumerate(frutas, start=1):
print(f"{i}: {fruta}")
Saída:
1: maçã
2: banana
3: laranja
A segunda é zip(), que percorre dois iteráveis em paralelo — útil sempre que dados relacionados estão em listas separadas:
nomes = ["Ana", "Bruno", "Carla"]
notas = [8.5, 7.0, 9.2]
for nome, nota in zip(nomes, notas):
print(f"{nome}: {nota}")
Saída:
Ana: 8.5
Bruno: 7.0
Carla: 9.2
Um detalhe que costuma causar perda silenciosa de dados: zip() para quando o menor iterável acaba, e os elementos excedentes são simplesmente ignorados.
Completando o conjunto, reversed() percorre de trás para frente e sorted() percorre em ordem crescente, ambos sem alterar a lista original:
numeros = [1, 2, 3, 4, 5]
for n in reversed(numeros):
print(n)
Saída:
5
4
3
2
1
notas = [7.5, 9.0, 5.5, 8.0, 6.5]
for nota in sorted(notas):
print(nota)
Saída:
5.5
6.5
7.5
8.0
9.0
Nem sempre você sabe de antemão quantas iterações serão necessárias, e é aí que o while entra: ele executa enquanto a condição for verdadeira. Na forma mais direta, com um contador:
contador = 0
while contador < 5:
print(f"Contador: {contador}")
contador += 1
Saída:
Contador: 0
Contador: 1
Contador: 2
Contador: 3
Contador: 4
O caso realmente interessante para quem trabalha com modelos é treinar até atingir uma condição, e não um número fixo de rodadas — perda abaixo de um limiar, por exemplo:
perda = 1.0
epoca = 0
while perda > 0.1:
perda = perda * 0.7 # simulando redução da perda
epoca += 1
print(f"Época {epoca}: perda = {perda:.4f}")
print(f"\nTreinamento concluído em {epoca} épocas!")
Saída:
Época 1: perda = 0.7000
Época 2: perda = 0.4900
Época 3: perda = 0.3430
Época 4: perda = 0.2401
Época 5: perda = 0.1681
Época 6: perda = 0.1176
Época 7: perda = 0.0824
Treinamento concluído em 7 épocas!
Há ainda o loop deliberadamente infinito, que roda até que algo lá dentro decida pará-lo:
while True:
entrada = input("Digite um número positivo (ou 'sair'): ")
if entrada == "sair":
print("Encerrando.")
break
numero = float(entrada)
if numero > 0:
print(f"Raiz quadrada de {numero}: {numero ** 0.5:.4f}")
else:
print("O número precisa ser positivo. Tente novamente.")
Esse exemplo já usa break, que merece atenção junto com seu par, o continue. O break interrompe o loop na hora, abandonando as iterações restantes:
numeros = [3, 7, 2, 9, 1, 5, 8]
for num in numeros:
if num == 9:
print(f"Encontrado o 9! Parando a busca.")
break
print(f"Verificando: {num}")
Saída:
Verificando: 3
Verificando: 7
Verificando: 2
Encontrado o 9! Parando a busca.
Já o continue abandona apenas a iteração atual e segue para a próxima:
numeros = range(1, 11)
for num in numeros:
if num % 2 == 0:
continue # pula números pares
print(num)
Saída:
1
3
5
7
9
Os dois funcionam igualmente dentro de um while, e podem conviver no mesmo loop:
i = 0
while i < 20:
i += 1
if i % 3 == 0:
continue # pula múltiplos de 3
if i > 10:
break # para quando passa de 10
print(i)
Saída:
1
2
4
5
7
8
10
Existe ainda um recurso pouco conhecido e bastante elegante: loops em Python aceitam um bloco else, executado quando o loop chega ao fim naturalmente, sem ter sido interrompido por um break. Isso resolve com clareza o padrão "procurei e não achei":
# Buscando um elemento
numeros = [1, 3, 5, 7, 9]
busca = 6
for num in numeros:
if num == busca:
print(f"Encontrado: {busca}")
break
else:
print(f"{busca} não encontrado na lista")
Saída:
6 não encontrado na lista
Se busca fosse 5, o loop encontraria o número, executaria o break e o else não rodaria — exatamente o comportamento que se espera de uma busca bem-sucedida.
Loops também se aninham, e essa combinação é rotina em processamento de matrizes e em busca de hiperparâmetros. A tabuada é o exemplo clássico:
# Tabuada
for i in range(1, 4):
for j in range(1, 4):
print(f"{i} x {j} = {i*j}")
print("---")
Saída:
1 x 1 = 1
1 x 2 = 2
1 x 3 = 3
---
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
---
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
---
Trocando o conteúdo, a mesma estrutura vira um grid search, que você vai reencontrar mais adiante no curso:
learning_rates = [0.001, 0.01, 0.1]
batch_sizes = [16, 32, 64]
for lr in learning_rates:
for bs in batch_sizes:
print(f"Testando: learning_rate={lr}, batch_size={bs}")
Saída:
Testando: learning_rate=0.001, batch_size=16
Testando: learning_rate=0.001, batch_size=32
Testando: learning_rate=0.001, batch_size=64
Testando: learning_rate=0.01, batch_size=16
...
Quando o loop existe apenas para construir uma lista, o Python oferece uma forma condensada de escrevê-lo numa única linha: a list comprehension, cuja sintaxe básica é [expressao for item in iteravel]. Comparando os dois jeitos de fazer a mesma coisa:
nova_lista = [expressao for item in iteravel]
# Jeito tradicional
quadrados = []
for i in range(1, 6):
quadrados.append(i ** 2)
print(quadrados)
# Com list comprehension
quadrados = [i ** 2 for i in range(1, 6)]
print(quadrados)
Saída de ambos:
[1, 4, 9, 16, 25]
Acrescentando um if ao final, ela também filtra:
# Apenas os pares
pares = [i for i in range(1, 11) if i % 2 == 0]
print(pares)
Saída:
[2, 4, 6, 8, 10]
E a expressão à esquerda pode transformar cada item:
nomes = ["ana", "bruno", "carla"]
nomes_maiusculos = [nome.upper() for nome in nomes]
print(nomes_maiusculos)
Saída:
['ANA', 'BRUNO', 'CARLA']
Aplicado a dados, esse mesmo padrão resolve uma normalização simples em uma linha:
valores = [10, 20, 30, 40, 50]
maximo = max(valores)
normalizados = [v / maximo for v in valores]
print(normalizados)
Saída:
[0.2, 0.4, 0.6, 0.8, 1.0]
Três hábitos fazem diferença na qualidade do código com loops. O primeiro é preferir for a while sempre que o número de iterações for conhecido — o contador manual só adiciona chance de erro:
# Evite
i = 0
while i < len(lista):
print(lista[i])
i += 1
# Prefira
for item in lista:
print(item)
O segundo é nunca modificar uma lista enquanto itera sobre ela. O comportamento é imprevisível porque o índice interno continua avançando enquanto a lista encolhe, e itens acabam sendo pulados em silêncio:
# PERIGOSO — comportamento imprevisível
numeros = [1, 2, 3, 4, 5]
for n in numeros:
if n % 2 == 0:
numeros.remove(n) # NÃO faça isso
# CORRETO — itere sobre uma cópia ou use list comprehension
numeros = [1, 2, 3, 4, 5]
numeros = [n for n in numeros if n % 2 != 0]
print(numeros) # [1, 3, 5]
O terceiro já apareceu antes e merece ser repetido: use enumerate() no lugar de range(len()).
# Evite
for i in range(len(lista)):
print(i, lista[i])
# Prefira
for i, item in enumerate(lista):
print(i, item)
Para ver tudo isso funcionando junto, vale simular um pré-processamento de dataset como o que antecede o treino de qualquer modelo — com validação de registros, descarte controlado e cálculo de estatística no final:
dataset = [
{"nome": "Alice", "idade": 25, "salario": 3500.0},
{"nome": "Bob", "idade": -1, "salario": 4200.0}, # idade inválida
{"nome": "Carla", "idade": 30, "salario": None}, # salário ausente
{"nome": "Diego", "idade": 28, "salario": 5100.0},
{"nome": "Eva", "idade": 22, "salario": 2900.0},
]
registros_validos = []
erros = []
for i, registro in enumerate(dataset):
nome = registro["nome"]
# Verificar idade
if registro["idade"] <= 0:
erros.append(f"Registro {i} ({nome}): idade inválida")
continue
# Verificar salário
if registro["salario"] is None:
erros.append(f"Registro {i} ({nome}): salário ausente")
continue
registros_validos.append(registro)
# Calcular média de salários dos registros válidos
salarios = [r["salario"] for r in registros_validos]
media = sum(salarios) / len(salarios)
print("Registros válidos:")
for r in registros_validos:
print(f" {r['nome']}: R${r['salario']:.2f}")
print(f"\nMédia salarial: R${media:.2f}")
print("\nErros encontrados:")
for erro in erros:
print(f" {erro}")
Saída:
Registros válidos:
Alice: R$3500.00
Diego: R$5100.00
Eva: R$2900.00
Média salarial: R$3833.33
Erros encontrados:
Registro 1 (Bob): idade inválida
Registro 2 (Carla): salário ausente
Repare que o enumerate(), o continue e a list comprehension aparecem ali lado a lado, cada um no papel que lhe cabe — é assim que esses recursos costumam se combinar em código real.
Do que foi visto, o essencial é isto: o for itera sobre qualquer iterável e o range(start, stop, step) gera as sequências numéricas; enumerate() entrega índice e valor juntos, zip() percorre iteráveis em paralelo; o while serve quando o número de iterações é desconhecido; break encerra o loop e continue pula para a próxima iteração, sendo que o else do loop só roda se nenhum break aconteceu; a list comprehension constrói listas de forma compacta; e modificar uma lista durante a iteração continua sendo uma má ideia.
Os seis exercícios a seguir vão do uso básico de range() até a escolha entre loop tradicional e comprehension. Resolva cada um antes de abrir a resposta comentada.
-
Escreva um loop que imprima todos os números de 1 a 100 que sejam simultaneamente divisíveis por 3 e por 5. Use
range()e o operador%.✓ Resposta:for i in range(1, 101): if i % 3 == 0 and i % 5 == 0: print(i)Saída:
15 30 45 60 75 90Nota: um número divisível por 3 e por 5 é divisível por 15. Poderia também ser escrito como
if i % 15 == 0. -
Dada a lista abaixo, use list comprehension para criar uma nova lista contendo apenas os números negativos, com seus valores absolutos (positivos).
numeros = [4, -7, 0, 15, -3, 22, -9, 1, -14]Resultado esperado:
[7, 3, 9, 14]✓ Resposta:numeros = [4, -7, 0, 15, -3, 22, -9, 1, -14] negativos_absolutos = [abs(n) for n in numeros if n < 0] print(negativos_absolutos)Saída:
[7, 3, 9, 14]A list comprehension filtra apenas os negativos (
if n < 0) e aplicaabs()para pegar o valor absoluto. -
Sem rodar o código, diga o que será impresso e explique o comportamento do
elseno loop:valores = [2, 4, 6, 8, 10] for v in valores: if v % 3 == 0: print(f"Encontrado múltiplo de 3: {v}") break else: print("Nenhum múltiplo de 3 encontrado")✓ Resposta:O código imprimirá:
Nenhum múltiplo de 3 encontradoNenhum dos valores
[2, 4, 6, 8, 10]é divisível por 3. Portanto obreaknunca é executado, o loop termina normalmente, e o blocoelseé executado. Se a lista contivesse o número 6, por exemplo, o programa imprimiria"Encontrado múltiplo de 3: 6"e oelseNÃO seria executado, pois obreakteria interrompido o loop. -
Explique por que o código abaixo pode ter comportamento inesperado e reescreva-o de forma correta:
lista = [1, 2, 3, 4, 5, 6] for n in lista: if n % 2 == 0: lista.remove(n) print(lista)✓ Resposta:O problema é que Python itera sobre a lista usando índices internos. Quando um elemento é removido, os índices de todos os elementos seguintes se deslocam, fazendo com que alguns elementos sejam pulados.
Para a lista
[1, 2, 3, 4, 5, 6], o resultado esperado seria[1, 3, 5], mas o código produz[1, 3, 5]às vezes e em outras situações pula elementos. O comportamento é imprevisível e dependente do Python runtime.Forma correta usando list comprehension:
lista = [1, 2, 3, 4, 5, 6] lista = [n for n in lista if n % 2 != 0] print(lista)Saída:
[1, 3, 5] -
Use
zip()eenumerate()juntos para imprimir a posição e os dados de duas listas pareadas:produtos = ["notebook", "mouse", "teclado"] precos = [3500.00, 89.90, 149.90]Saída esperada:
1. notebook — R$3500.00 2. mouse — R$89.90 3. teclado — R$149.90✓ Resposta:produtos = ["notebook", "mouse", "teclado"] precos = [3500.00, 89.90, 149.90] for i, (produto, preco) in enumerate(zip(produtos, precos), start=1): print(f"{i}. {produto} — R${preco:.2f}")Saída:
1. notebook — R$3500.00 2. mouse — R$89.90 3. teclado — R$149.90O
zip()combina as duas listas em pares, e oenumerate()adiciona o índice. Ostart=1faz a contagem começar em 1. -
Escreva um loop
whileque simule o processo de treinamento de um modelo. Comece comperda = 100.0e a cada iteração reduza a perda em 15%. O loop deve parar quando a perda for menor que 1.0 ou quando atingir 50 épocas, o que vier primeiro. Ao final, imprima quantas épocas foram necessárias e qual foi a perda final.✓ Resposta:perda = 100.0 epoca = 0 max_epocas = 50 while perda >= 1.0 and epoca < max_epocas: perda = perda * 0.85 epoca += 1 print(f"Épocas executadas: {epoca}") print(f"Perda final: {perda:.4f}") if perda < 1.0: print("Parou por convergência (perda < 1.0)") else: print("Parou por atingir o máximo de épocas")Saída:
Épocas executadas: 29 Perda final: 0.9787 Parou por convergência (perda < 1.0)
Para aprofundar:
- Documentação oficial do Python — range: https://docs.python.org/3/library/stdtypes.html#range
- Documentação oficial do Python — Funções embutidas (enumerate, zip, sorted): https://docs.python.org/3/library/functions.html
- Python Tutorial oficial — Estruturas de dados e loops: https://docs.python.org/3/tutorial/datastructures.html
- PEP 202 — List Comprehensions: https://peps.python.org/pep-0202/
- Real Python — Python "for" Loops: https://realpython.com/python-for-loop/
- Real Python — Python "while" Loops: https://realpython.com/python-while-loop/
- Real Python — List Comprehensions in Python: https://realpython.com/list-comprehension-python/