O capítulo anterior terminou com uma questão no ar. Após estudarmos todas as formas de se usar o comando if, restou o desafio de usar um bloco elif para consertar um pequeno defeito no programa despdom2.py. O bug se manifesta quando os gastos de Ana e Bia são iguais. Nesse caso, o programa escreve na tela:
Bia deve pagar: R$ 0.0
Em vez de fazer a Bia escrever um cheque de zero reais, o melhor seria tratar esse caso especial. Veja como fazê-lo, usando uma construção if/elif/else (listagem 1). Se você guardou o arquivo despdom2.py da lição anterior, terá muito pouco o que digitar. Abra-o e salve com o nome de despdom3.py. O código é idêntico à versão anterior até a linha 14. Ali, você faz a primeira alteração: o else é substituído por um elif que verifica se Bia gastou menos que a média. As linhas 15 e 16 continuam como antes, mas agora elas só serão executadas se bia < media for verdadeiro. As linhas 17 e 18 são novas, e servem para tratar o caso em que nem ana < media nem bia < media, ou seja, quando não há diferença a ser paga. Agora você pode testar o programa digitando valores diferentes e depois valores iguais para as despesas de Ana e Bia.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # despdom3.py - Calculadora de despesas domesticas - versao 3
print 'Balanco de despesas domesticas'
ana = float(raw_input('Quanto gastou Ana? '))
bia = float(raw_input('Quanto gastou Bia? '))
print
total = ana + bia
print 'Total de gastos: R$ %s' % total
media = total/2
print 'Gastos por pessoa: R$ %s' % media
if ana < media:
diferenca = media - ana
print 'Ana deve pagar: R$ %s' % diferenca
elif bia < media:
diferenca = media - bia
print 'Bia deve pagar: R$ %s' % diferenca
else:
print 'Ana e Bia gastaram a mesma Quantia.'
|
Logo adiante iremos reescrever o programinha acima para torná-lo mais flexível, permitindo digitar os nomes e os gastos de qualquer número de pessoas. Assim ele será útil para repartir as contas de uma viagem de férias ou daquela festa entre amigos. Para começar, vamos construir um programa um pouco mais simples, capaz de somar uma série de números (listagem 2).
1 2 3 4 5 6 7 8 9 10 | # somadora1.py - somadora infinita - versao 1
print 'Digite os valores a somar seguidos de [ENTER].'
print 'Para encerrar digite zero: 0'
n = float(raw_input(':'))
total = n
while n != 0:
n = float(raw_input(':'))
total = total + n
print 'TOTAL: %s' % total
|
Vamos ver o que faz esse programa, linha por linha.
Os comandos while e for são semelhantes por causarem a repetição de um bloco. Ambos são chamados, pelos computólogos, de comandos de iteração (iteração é sinônimo de repetição; não confunda com “interação”, que é uma ação recíproca entre dois ou mais agentes).
A diferença é que no comando for a iteração serve para percorrer uma lista de itens, como fizemos anteriormente quando trabalhamos com tabelas de conversão. No caso do for, o número de repetições é sempre conhecido de antemão: o bloco será executado uma vez para cada item da lista. O comando while serve para todos os outros casos de iteração, quando o número de repetições é indefinido. Nossa somadora infinita é um exemplo típico: a iteração que solicita valores e os totaliza poderá ser repetida qualquer número de vezes, dependendo apenas da sua vontade.
Agora vamos analisar de perto duas circunstâncias especiais. Rode o programa e digite 0 (zero) como primeiro valor. Nas linhas 5 e 6 o programa armazenará o zero nas variáveis n e total. A seguir, na linha 7, o comando while verificará a condição n != 0. Nesse caso, a condição será falsa. Então o bloco subordinado ao while não será executado nenhuma vez, e o programa passará direto para a linha 10, mostrando o total.
Outro momento interessante ocorre quando o primeiro valor digitado não é zero, e a iteração é executada. Digamos que o usuário digitou [1][Enter], [2][Enter] e [0][Enter]. O zero digitado pelo usuário será lido e armazenado em n na linha 8, como já vimos. Na linha 9 o valor de n é somado ao total. Nessa iteração o valor de n é zero, portanto estamos somando zero ao total, uma operação inofensiva. Só após efetuar essa soma inútil, o programa retornará ao início do bloco e verificará que a condição do while não é mais verdadeira, pois agora nosso n é igual a zero. É importante perceber que, apesar de o valor de n passar a ser zero na linha 8, a execução continua até o fim do bloco, passando pela linha 9, para só então ocorrer o retorno ao início do bloco e a verificação da condição de continuidade da repetição.
Quando estudamos as condições lógicas no final do capítulo anterior, aprendemos que Python considera o valor 0 (zero) como sinônimo de “falso”, e valores não-zero como “verdadeiros”. Programadores experientes em Python costumam tirar proveito desse fato para abreviar as condições que colocam em seus ifs e whiles. Em nosso programa somadora1.py, a linha 7:
while n != 0:
pode ser escrita de forma mais abreviada assim:
while n:
Faça essa alteração no programa e experimente. Você verá que nada mudou no seu funcionamento. Isso porque, quando n é diferente de zero, a condição “n” expressa em while n: é considerada verdadeira, e a iteração é executada. Quando n passa a ser zero, a condição é falsa, encerrando a iteração.
Outra forma de escrever a somadora, mais elegante em minha opinião, é a mostrada na listagem 3.
1 2 3 4 5 6 7 8 9 10 | # somadora2.py - somadora infinita - versao 2
print 'Digite os valores a somar seguidos de [ENTER].'
print 'Para encerrar digite zero: 0'
total = 0
while True:
n = float(raw_input(':'))
if n == 0: break
total = total + n
print 'TOTAL: %s' % total
|
Aqui a lógica é um pouco diferente: na linha 6 o loop while tem como condição a constante True, ou “verdadeiro”. Assim o loop das linhas 6 a 9 seria repetido infinitas vezes, em tese. Na prática, a linha 8 verifica se o valor de n é zero. Em caso afirmativo, o comando “break” é acionado. Isso faz com que o loop while seja interrompido imediatamente, e a execução do programa passa diretamente para a próxima linha após o bloco (linha 10 em nosso exemplo).
Essa forma de codificar, usando loops infinitos com breaks, não está de acordo com a Programação Estruturada, a filosofia dominante entre os programadores nos anos 70. O problema é que não fica imediatamente aparente qual é a condição de terminação do loop e alguns professores de computação podem descontar pontos por isso. Mas em se tratando de um bloco de apenas três linhas, não acho que isso seja um grande problema. A vantagem é que agora a função de leitura de dados ocorre em apenas um lugar no programa (na linha 7) e não em dois, como na versão anterior (linhas 5 e 8 de somadora1.py). Isso simplificará nossa próxima alteração. Além disso, não acontece mais a totalização inútil da linha 9, somando zero ao total na saída, porque o comando break da linha 8 faz o programa passar direto para a linha 10.
Uma forma mais natural de codificar esse loop seria usar comandos com o do/while ou repeat/until existentes em linguagens como C/C++/Java e Pascal/Delphi; nessas estruturas de controle, o teste é feito no fim do loop, garantindo a execução do bloco ao menos uma vez. É o que precisamos fazer aqui, mas Python não possui um comando de loop especial para essa situação. Vejamos outro exemplo.
Suponha que você queira, por algum motivo estranho, somar os números naturais (1, 2, 3 etc.) até obter um total maior ou igual a 100. Observe na listagem 4 como ficaria o loop central para fazer isso em Delphi, Java e Python.
REPEAT
n := n + 1;
total := total + n;
UNTIL (total >= 100);
do {
n = n + 1;
total = total + n;
} while (total < 100);
while True:
n = n + 1
total = total + n
if total >= 100: break
Note que os três programas acima estão incompletos; reproduzimos apenas o loop principal. Generalizando, qualquer loop com teste no final pode ser codificado em Python usando-se uma combinação de while True e if/break, assim:
1 2 3 4 5 | while True:
comando1
comando2
# etc.
if condicao_final: break
|
Um defeito das nossas somadoras, e de todos os programas que fizemos até agora, é que eles não toleram falhas na digitação. Se você rodar o programa somadora2.py e digitar apenas [Enter] para encerrar, verá a seguinte mensagem na tela:
Traceback (innermost last):
File 'somadora1.py', line 7, in ?
n = float(raw_input())
ValueError: empty string for float()
A segunda linha dessa mensagem identifica o local do erro: linha 7 do arquivo (file) somadora1.py. Na terceira linha está reproduzida a linha do programa onde ocorreu o problema, e a mensagem final informa qual foi o erro. Podemos traduzí-la assim: “Erro de valor: ‘’string’’ vazia para a função float()”.
O problema é que, ao digitarmos [Enter] sem fornecer um número, a função raw_input() retorna uma ‘’string’’ vazia (nada mais justo, pois nada foi digitado). Em seguinda, a função float() tenta transformar a ‘’string’’ vazia em um ponto flutuante, mas não sabe como. É ela que dispara a mensagem de erro, fazendo com que o programa seja interrompido antes de mostrar o valor total da soma.
Efeito semelhante pode ser obtido se você digitar um texto qualquer em vez de um número. Experimente.
Nesse caso, a mensagem de erro final é: “ValueError: invalid literal for ‘’float’‘(): blah”. Nesse caso, a reclamação é de “’‘invalid literal’‘”, significando que o texto fornecido para a função float() não se parece com um número.
A melhor maneira de resolver esse problema envolve o uso de mais uma comando de bloco de Python: o conjunto try/except (tentar/exceto). Esse par de palavras-chave formam o mecanismo de “tratamento de exceções” de Python, algo que só se encontra em linguagens bastante modernas como Java e as versões mais recentes de C++. A idéia básica é simples: no caso da nossa somadora, vamos tentar (try) converter a ‘’string’’ digitada em ‘’float’‘; se isso não der certo, temos uma exceção, que deve ter tratamento especial. No nosso caso, vamos simplesmente acionar o comando break para interromper o ‘’loop’’ e exibir a totalização.
Veja na listagem abaixo como fica a somadora3.py, agora com tratamento de exceções.
1 2 3 4 5 6 7 8 9 10 11 12 | # somadora3.py - somadora infinita - versao 3
print 'Digite os valores a somar seguidos de [ENTER].'
print 'Para encerrar apenas [ENTER].'
total = 0
while True:
try:
n = float(raw_input(':'))
total = total + n
except:
break
print 'TOTAL: %s' % total
|
Vamos comentar apenas as diferenças em relação à versão anterior:
Experimente o programa agora: ele ficou muito mais conveniente de usar. Para interromper a soma e obter o total, basta teclar [Enter] em uma linha em branco. Uma boa melhoria na “usabilidade” da somadora!
A terceira versão da nossa somadora ainda não chegou lá: tratamos da mesma forma a situação em que usuário não digitou nada e aquela onde ele digitou algo que não é um número válido em Python. Pode ser que o usuário seja um datilógrafo à moda antiga, que digita L minúsculo no lugar do dígito 1. Ou ainda alguém que quer usar, com toda razão, a ”,” como separador decimal (Python só aceita números com ponto decimal). Para diferenciar um tipo de erro do outro, e saber quando o usuário apenas quer encerrar o programa, precisamos guardar a linha que ele digitou antes de tentar transformá-la em um número. Veja como na listagem abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # somadora4.py - somadora infinita - versao 4
print 'Digite os valores a somar seguidos de .'
print 'Para encerrar apenas .'
total = 0
while True:
try:
linha = raw_input(':')
n = float(linha)
total = total + n
except:
if len(linha) == 0:
break
elif ',' in linha:
print 'Use o . (ponto) como separador decimal.'
else:
print 'Isso nao parece um numero valido.'
print 'TOTAL: %s' % total
|
Vamos analisar as novidades dessa versão:
Voltemos ao problema do cálculo de despesas. Nossa meta é fazer um programa que seja capaz de calcular a partilha de gastos de qualquer grupo de pessoas, e não apenas de Ana e Bia. Para isso, vamos precisar associar o nome das pessoas aos seus respectivos gastos. A linguagem Python possui uma estrutura de dados ideal para essa aplicação. É o dicionário, conhecido pelos programadores Perl como ‘’hash’’ ou associação. Como ocorre em Perl, em Python o dicionário serve para associar chaves a valores. O mais comum é que as chaves sejam ‘’strings’‘, como no nosso caso, onde as chaves serão nomes de pessoas. Mas as chaves podem ser qualquer tipo de objeto.
Em Python o dicionário é bem mais poderoso que em Perl, pois seus valores podem conter qualquer tipo de objeto como listas e até mesmo outros dicionários. Para entender rapidamente o funcionamento de um dicionário, nada melhor que experimentar com o interpretador interativo IDLE. Faça os seguintes testes, que explicaremos a seguir, com a abaixo:
Python (#0, Apr 13 1999, 10:51:12) [MSC 32 bit (Intel)] on win32
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> dic = {}
>>> dic['ze'] = 300
>>> dic['mauricio'] = 100
>>> dic['heloisa'] = 150
>>> dic['ze']
300
>>> dic
{'mauricio': 100, 'ze': 300, 'heloisa': 150}
>>> dic['ze'] = 200
>>> dic
{'mauricio': 100, 'ze': 200, 'heloisa': 150}
>>> dic.keys()
['mauricio', 'ze', 'heloisa']
>>> dic['paulo']
Traceback (innermost last):
File '', line 1, in ?
dic['paulo']
KeyError: paulo
>>> dic.has_key('heloisa')
True
>>> dic.has_key('paulo')
False
>>>
Agora que conhecemos o funcionamento básico dos dicionários, podemos implementar o nosso aplicativo de acerto de contas, que pode ser muito útil por exemplo na administração de uma república de universitários. Antes de mais nada, vejamos como vai funcionar o programa:
C:\PythonXX\Curso>python desprep1.py
Balanco de despesas da Republica Recanto Suico
(deixe um nome em branco para encerrar)
Digite o nome da pessoa: Marcos
Quanto gastou Marcos? 10
Digite o nome da pessoa: Alexandre
Quanto gastou Alexandre? 500
Digite o nome da pessoa: Tyrone
Quanto gastou Tyrone? 250
Digite o nome da pessoa: Harley
Quanto gastou Harley? 124,67
Numero invalido.
Quanto gastou Harley? 124.67
Digite o nome da pessoa:
Numero de pessoas: 4
Total de gastos: R$ 884.67
Gastos por pessoa: R$ 221.17
Saldo de Marcos: -211.17
Saldo de Alexandre: 278.83
Saldo de Tyrone: 28.83
Saldo de Harley: -96.50
C:\PythonXX\Curso>
Agora, vamos à listagem do programa desprep1.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #desprep1.py - calculo de despesas da republica
print 'Balanco de despesas da Republica Recanto Suico'
print
print '(deixe um nome em branco para encerrar)'
print
total = 0
contas = {}
while True:
pessoa = raw_input('Digite o nome da pessoa: ')
if not pessoa: break
while True:
resp = raw_input('Quanto gastou %s? ' % pessoa)
try:
gasto = float(resp)
break
except:
print 'Numero invalido.'
contas[pessoa] = gasto
total = total + gasto
num_pessoas = len(contas)
print
print 'Numero de pessoas: %d' % num_pessoas
print 'Total de gastos: R$ %.2f' % total
media = total/num_pessoas
print 'Gastos por pessoa: R$ %.2f' % media
print
for nome in contas.keys():
saldo = contas[nome] - media
print 'Saldo de %s: %.2f' % (nome, saldo)
|
Agora já sabemos tudo o que precisávamos para implementar um jogo simples, como havíamos prometido no capítulo anterior. Trata-se de uma simulação de pouso lunar, em modo texto. Esse programinha é baseado em um jogo clássico escrito para calculadoras HP-25. Nossa versão é bem mais fácil de entender que o original para calculadora. Em vez de explicar linha por linha o funcionamento do programa, colocamos comentários abundantes na própria listagem, delimitados pelo sinal #. Lembre-se de que não é preciso digitar os comentários (e o programa inteiro pode ser simplesmente copiado aqui no site). Esse simulador de alunissagem é um game de recursos mínimos, mas ainda assim deve valer alguns minutos de diversão, especialmente se você curte a física newtoniana ensinada no colegial.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | # lunar.py
# O jogo da alunissagem
# importar funcao sqrt do modulo math
from math import sqrt
x = 500. # altitude em pes
v = -50. # velocidade em pes/s
g = -5. # aceleracao gravitacional lunar em pes/s/s
t = 1. # tempo entre jogadas em segundos
comb = 120. # quantidade de combustível
print 'Simulacao de alunissagem'
print
print '(digite a quantidade de combustivel a queimar)'
fmt = 'Alt: %6.2f Vel: %6.2f Comb: %3d'
while x > 0: # enquanto nao tocamos o solo
msg = fmt % (x, v, comb) # montar mensagem
if comb > 0: # ainda temos combustivel?
# obter quantidade de combustivel a queimar
resp = raw_input(msg + ' Queima = ')
try: # converter resposta em numero
queima = float(resp)
except: # a resposta nao era um numero
queima = 0
if queima > comb: # queimou mais do que tinha?
queima = comb # entao queima o que tem
comb = comb - queima # subtrai queimado
a = g + queima # acel = grav + queima
else: # sem combustivel
print msg # mensagem sem perguntar
a = g # aceleracao = gravidade
x0 = x # armazenar posicao inicial
v0 = v # armazenar velocidade inicial
x = x0 + v0*t + a*t*t/2 # calc. nova posicao
v = v0 + a*t # calc. nova vel.
# se o loop acabou, tocamos no solo (x <= 0)
vf = sqrt(v0*v0 + 2*-a*x0) # calcular vel. final
print '>>>CONTATO! Velocidade final: %6.2f' % (-vf)
# avaliar pouso de acordo com a velocidade final
if vf == 0:
msg = 'Alunissagem perfeita!'
elif vf <= 2:
msg = 'Alunissagem dentro do padrao.'
elif vf <= 10:
msg = 'Alunissagem com avarias leves.'
elif vf <= 20:
msg = 'Alunissagem com avarias severas.'
else:
msg = 'Modulo lunar destruido no impacto.'
print '>>>' + msg
|
Seu objetivo é desacelerar a nave, queimando combustível na dosagem certa ao longo da queda, para tocar o solo lunar com uma velocidade bem próxima de zero. Se você quiser, pode usar um diagrama como o mostrado abaixo (colocamos em nosso site um desses em branco, para você imprimir e usar). As unidades estão no sistema inglês, como no original. O mais importante é você saber que cada 5 unidades de combustível queimadas anulam a aceleração da gravidade. Se queimar mais do que 5 unidades, você desacelera; menos do que 5, você ganha velocidade. Primeiro, pratique seus pousos preocupando-se apenas com a velocidade final. Depois você pode aumentar a dificuldade, estabelecendo um limite de tempo: por exemplo, o pouso tem que ocorrer em exatos 13 segundos. Uma última dica: cuidado para não queimar combustível cedo demais. Se você subir, vai acabar caindo de uma altura ainda maior! Boas alunissagens!