7. while: repetir enquanto¶
Nesta aula
Ideia central: um laço é uma condição que o programa volta a testar. O
if pergunta uma vez; o while pergunta, executa, e pergunta de novo —
até a resposta virar falsa.
Ao fim você vai conseguir: escrever um while com contador e com
acumulador; parar um laço quando a pessoa digita um valor combinado
(sentinela); rastrear no papel o valor das variáveis volta a volta; e
reconhecer as duas maneiras de um laço nunca terminar.
O problema¶
Este programa avisa o cliente sobre o consumo dos cinco primeiros dias do mês:
# O aviso de consumo dos 5 primeiros dias, sem laco nenhum.
print("Dia 1: verifique seu consumo")
print("Dia 2: verifique seu consumo")
print("Dia 3: verifique seu consumo")
print("Dia 4: verifique seu consumo")
print("Dia 5: verifique seu consumo")
Dia 1: verifique seu consumo
Dia 2: verifique seu consumo
Dia 3: verifique seu consumo
Dia 4: verifique seu consumo
Dia 5: verifique seu consumo
Funciona. Agora imagine o mês inteiro: trinta linhas quase iguais, e quando o texto do aviso mudar você tem de trocar trinta vezes — e vai esquecer uma.
Pior: e se o número de dias só for conhecido quando o programa rodar? Não dá para escrever "N linhas" num arquivo que já está escrito.
Repetir é trabalho de computador. O que o programa precisa é de uma forma de dizer "faça isto de novo enquanto ainda fizer sentido".
A estrutura do while¶
# O MESMO aviso, com um contador e um while.
dia = 1
while dia <= 5:
print(f"Dia {dia}: verifique seu consumo")
dia = dia + 1
print("Fim da semana.")
Dia 1: verifique seu consumo
Dia 2: verifique seu consumo
Dia 3: verifique seu consumo
Dia 4: verifique seu consumo
Dia 5: verifique seu consumo
Fim da semana.
Cinco linhas produziram a mesma saída das seis anteriores — e produziriam trinta linhas trocando um único número.
A forma é quase a do if que você viu na Unidade 2:
A diferença é uma palavra e é a palavra toda: o if testa uma vez, o while
volta. Quando o bloco indentado termina, o programa não segue em frente — ele
sobe de volta para a linha do while e testa a condição outra vez. Só quando o
teste der False é que a execução continua na primeira linha não indentada.
💡 Pense assim: a escada de um prédio
"Enquanto eu não estiver no meu andar, suba mais um degrau."
A cada degrau você faz a mesma pergunta — cheguei? — e o que muda é a sua posição. Se a sua posição não mudasse, você ficaria perguntando para sempre, parado no mesmo degrau. É exatamente o que acontece com um laço sem a linha que faz a variável avançar.
As três peças¶
Todo while que termina tem estas três, e a maioria dos laços quebrados é um
laço a que falta uma delas:
| Peça | No exemplo acima | O que acontece sem ela |
|---|---|---|
| início — a variável existe antes do laço | dia = 1 |
NameError na primeira volta |
| teste — a condição de continuar | while dia <= 5: |
não é um laço |
| avanço — algo muda dentro do bloco | dia = dia + 1 |
roda para sempre |
O dia = dia + 1 é o mesmo = do capítulo 2: calcule o lado direito e guarde
no nome da esquerda. Ele não é uma equação; é o degrau da escada.
Rastrear volta a volta¶
Esta é a habilidade central do capítulo, e é o que a prova cobra. Rastrear um laço é a mesma tabela do capítulo 2, com uma linha por volta em vez de uma linha por instrução:
| Volta | dia no teste |
dia <= 5 |
Imprime | dia no fim da volta |
|---|---|---|---|---|
| 1 | 1 | True |
Dia 1: ... |
2 |
| 2 | 2 | True |
Dia 2: ... |
3 |
| 3 | 3 | True |
Dia 3: ... |
4 |
| 4 | 4 | True |
Dia 4: ... |
5 |
| 5 | 5 | True |
Dia 5: ... |
6 |
| — | 6 | False |
— | 6 |
Repare na última linha: o teste é feito uma vez a mais do que o corpo do
laço. O laço deu cinco voltas, mas a condição foi avaliada seis vezes. É por
isso que, ao fim, dia vale 6 e não 5 — e é a causa mais comum de erro de um a
mais ou um a menos.
Onde o <= importa
while dia <= 5 dá cinco voltas. while dia < 5 dá quatro. Não é
preciosismo: é uma noite inteira de laboratório procurando o dia que sumiu.
O acumulador¶
Contar voltas é útil. Juntar valores ao longo das voltas é o que faz o laço valer a pena.
# ACUMULADOR: uma variavel que guarda o que ja foi somado.
total = 0
numero = 1
while numero <= 10:
total = total + numero
numero = numero + 1
print("Soma de 1 ate 10:", total)
O padrão se chama acumulador e tem sempre esta forma:
- uma variável começa em zero antes do laço;
- dentro do laço ela recebe
variavel = variavel + alguma_coisa; - depois do laço ela vale o total.
Rastreie:
| Volta | total no início |
numero |
total no fim |
|---|---|---|---|
| 1 | 0 | 1 | 1 |
| 2 | 1 | 2 | 3 |
| 3 | 3 | 3 | 6 |
| 4 | 6 | 4 | 10 |
| 5 | 10 | 5 | 15 |
...e assim até numero chegar a 11, quando o teste falha e o total é 55.
💡 Pense assim: o carrinho do supermercado
O carrinho chega vazio — é o total = 0. Cada produto que entra soma ao que
já estava lá. No caixa, o carrinho tem a compra inteira.
Se você despejasse o carrinho a cada corredor, chegaria ao caixa com o último produto e nada mais. Guarde essa imagem: ela é o erro da próxima seção.
Contador e acumulador no mesmo laço¶
Eles convivem sem problema, e quase sempre é isso que se quer:
# Contador e acumulador no mesmo laco: em que dia a franquia estoura.
franquia_gb = 20
usado_gb = 0
dias = 0
while usado_gb <= franquia_gb:
consumo = float(input(f"Consumo do dia {dias + 1} em GB: "))
usado_gb = usado_gb + consumo
dias = dias + 1
print(f"A franquia estourou no dia {dias}")
print(f"Consumo acumulado: {usado_gb:.1f} GB de {franquia_gb} GB")
Consumo do dia 1 em GB: 1.4
Consumo do dia 2 em GB: 3.2
Consumo do dia 3 em GB: 0.8
Consumo do dia 4 em GB: 5.5
Consumo do dia 5 em GB: 2.1
Consumo do dia 6 em GB: 4.0
Consumo do dia 7 em GB: 3.9
A franquia estourou no dia 7
Consumo acumulado: 20.9 GB de 20 GB
É aqui que o laço mostra a que veio. Se o cliente gastasse sempre 1,4 GB por
dia, nem precisaria de laço: 20 // 1.4 responde 14 dias numa linha só. Mas o
consumo muda todo dia, e na hora de escrever o programa ninguém sabe quantos
dias vão caber na franquia — podem ser sete, podem ser trinta. Não há conta a
fazer de antemão: o jeito é ir somando dia a dia e perguntar, a cada volta, se
a franquia já estourou.
Repare também em qual dia sai na resposta. O teste acontece antes de ler o consumo, então o dia que passa dos 20 GB é lido e somado, e só depois a condição fica falsa. Por isso o programa diz "estourou no dia 7": é exatamente o dia em que a operadora precisa avisar o cliente.
O erro que não dá erro, versão laço¶
Este é o momento central da unidade. Os dois laços abaixo são idênticos, linha por linha, a não ser pelo lugar onde o acumulador é criado:
# O MESMO laco, com o acumulador criado no lugar errado.
total_certo = 0
numero = 1
while numero <= 5:
total_certo = total_certo + numero
numero = numero + 1
print("Certo: ", total_certo)
numero = 1
while numero <= 5:
total_errado = 0 # zerado a cada volta
total_errado = total_errado + numero
numero = numero + 1
print("Errado:", total_errado)
1 + 2 + 3 + 4 + 5 dá 15. O segundo laço imprimiu 5.
E não deu erro nenhum. Nenhum aviso, nenhum traceback, nenhuma linha vermelha. Ele rodou até o fim, imprimiu um número inteiro perfeitamente plausível e seguiu a vida.
O motivo aparece na hora quando se rastreia:
| Volta | total_errado no início |
zera para | soma | total_errado no fim |
|---|---|---|---|---|
| 1 | — | 0 | 1 | 1 |
| 2 | 1 | 0 | 2 | 2 |
| 3 | 2 | 0 | 3 | 3 |
| 4 | 3 | 0 | 4 | 4 |
| 5 | 4 | 0 | 5 | 5 |
A linha total_errado = 0 está dentro do laço. Ela não roda uma vez: roda
cinco vezes, jogando fora tudo o que já havia sido somado. No fim, a variável
não guarda a soma — guarda o último valor, e só.
Por que este é o pior defeito possível
É o mesmo modo de falha da Lista 03 (input sem converter) e da Lista 04
(precedência), e agora com um agravante: o resultado errado é plausível.
Uma soma de cinco parcelas dando 5 não choca ninguém que não conheça as
parcelas.
Com trinta dias de consumo, o programa diria que o cliente gastou 1,2 GB no mês — o valor do último dia. Ninguém trava, ninguém reclama, e a conta vai errada para o cliente.
A defesa é uma pergunta, sempre a mesma: este número faz sentido? Some dois ou três valores de cabeça e compare. Um computador nunca vai te avisar disso.
Regra prática: o acumulador é criado onde ele tem de valer zero uma única
vez — antes do while, na margem, sem indentação.
A sentinela: parar quando a pessoa mandar¶
Na franquia, quem decidia a hora de parar era a conta. Quando quem decide é a pessoa que está usando o programa, combina-se um valor que significa acabou. Esse valor se chama sentinela.
# SENTINELA: o laco para quando a pessoa digita o valor combinado.
total = 0
dias = 0
consumo = float(input("Consumo do dia em GB (0 para encerrar): "))
while consumo != 0:
total = total + consumo
dias = dias + 1
consumo = float(input("Consumo do dia em GB (0 para encerrar): "))
print(f"Dias informados: {dias}")
print(f"Consumo total: {total:.1f} GB")
Consumo do dia em GB (0 para encerrar): Consumo do dia em GB (0 para encerrar): Consumo do dia em GB (0 para encerrar): Consumo do dia em GB (0 para encerrar): Consumo do dia em GB (0 para encerrar): Dias informados: 4
Consumo total: 7.1 GB
A saída acima é a saída padrão crua: como no capítulo 3, o Python não reimprime o que você digita, então os convites ficam grudados. Na tela do OnlineGDB a mesma execução aparece assim:
Consumo do dia em GB (0 para encerrar): 1.4
Consumo do dia em GB (0 para encerrar): 2.0
Consumo do dia em GB (0 para encerrar): 0.6
Consumo do dia em GB (0 para encerrar): 3.1
Consumo do dia em GB (0 para encerrar): 0
Dias informados: 4
Consumo total: 7.1 GB
Note o detalhe que confunde todo mundo: há dois input() no programa, e
isso não é descuido.
- o primeiro, antes do laço, existe para a condição ter o que testar na primeira volta;
- o segundo, no fim do bloco, é o "avanço" — sem ele, a condição testaria para sempre o mesmo valor.
💡 Pense assim: a catraca do ônibus
A catraca pergunta uma vez por pessoa. Alguém tem de estar na frente dela
antes da primeira pergunta — é o input() de fora. E, depois de cada
passagem, a próxima pessoa precisa chegar — é o input() de dentro.
E repare: o valor 0 não entrou na conta. Quando ele é lido, a condição
falha e o bloco não roda. A sentinela é a senha para sair, não um dado.
O laço infinito¶
É o erro mais barulhento do capítulo, e o único que se percebe na hora: o programa não termina.
Há exatamente duas causas.
1. Falta o avanço. A variável do teste nunca muda:
dia vale 1 para sempre, 1 <= 5 é eterno, e a tela enche de "Dia 1". Faltou
o dia = dia + 1.
2. O avanço vai para o lado errado. A variável muda, mas se afasta da condição de parada:
Aqui o saldo cresce, e saldo_gb > 0 fica cada vez mais verdadeiro. Era
- 1.
Uma variante da mesma causa é a sentinela que nunca chega: um laço que espera
consumo != 0 mas cujo input() ficou fora do bloco lê o mesmo valor eternamente.
Como sair
No OnlineGDB, o botão vermelho Stop acima do terminal. Rodando no seu
computador, Ctrl+C no terminal.
E o mais importante: não é um defeito do computador, e não perde o seu código. É o programa fazendo exatamente o que você escreveu. Pare, releia o bloco e procure a linha que deveria mudar a variável do teste.
Faça isto antes de rodar qualquer while
Ponha o dedo na condição e pergunte: qual linha, dentro do bloco, muda o valor que está sendo testado? Se você não achar essa linha, não rode — o laço é infinito.
Um erro que dá erro¶
Para variar, um que o Python denuncia na cara:
O while termina em dois-pontos e exige um bloco indentado, exatamente como
o if. A mensagem até diz em que linha o while estava.
Erros comuns deste capítulo¶
| Mensagem ou sintoma | Causa provável |
|---|---|
| o programa não para e a tela enche | falta o avanço, ou ele vai para o lado errado |
| o total é igual à última parcela | o acumulador foi zerado dentro do laço |
| o total deu zero | o acumulador está sendo somado fora do bloco (indentação) |
| deu uma volta a menos (ou a mais) | < onde devia ser <=, ou vice-versa |
IndentationError: expected an indented block after 'while' |
faltou indentar o corpo do laço |
NameError na primeira volta |
a variável do teste não foi criada antes do while |
| a sentinela entrou na soma | o valor está sendo somado antes de a condição ser testada |
| a sentinela nunca funciona | falta o segundo input(), dentro do laço |
Resumo¶
while condição:repete o bloco indentado enquanto a condição for verdadeira; oifpergunta uma vez, owhilevolta a perguntar.- Todo laço que termina tem três peças: início, teste e avanço.
- A condição é testada uma vez a mais do que o corpo roda.
- Acumulador: a variável nasce em zero antes do laço e cresce dentro dele. Criada dentro, ela entrega o último valor em vez do total — e não dá erro nenhum.
- Sentinela: um valor combinado que encerra o laço. Precisa de um
input()antes do laço e outro no fim do bloco. - Laço infinito é o programa obedecendo: ou falta o avanço, ou ele vai para o
lado errado. Stop no OnlineGDB,
Ctrl+Cno terminal. - Rastreie no papel, uma linha por volta. É o que a prova cobra.
Para praticar¶
Lista 07 · Repetição com while. A Parte A é quase toda
de tabelas de rastreio — faça-as no papel, sem computador, antes de abrir o
OnlineGDB.