Seguem alguns comentários sobre o código:
Vc usou várias vezes str(input(mensagem))
. Mas veja na documentação que input
sempre retorna uma string, então usar str
para converter esta string em uma string é redundante e portanto desnecessário. Se quer uma string, use apenas input(mensagem)
e pronto.
Quanto a if type(numero) == float
, também é desnecessário. Quando vc faz float(algo)
para converter algo
em float
, das duas uma:
- A conversão funciona e vc obtém um
float
, ou - A conversão falha e é lançada uma exceção (no caso,
ValueError
)
Ou seja, se não for digitado um número válido, ele lança uma exceção e cai no bloco except
. E se digitar um número válido, o valor obtido já é um float
e portanto não precisa verificar o tipo. Ou seja, a função poderia ser apenas assim:
def validadorDeNumeroFloat(msg):
while True:
try:
return float(input(msg).replace(',', '.'))
except ValueError:
print('\033[31mERRO!! Valor invalido\033[m')
Também eliminei as variáveis intermediárias, que não estavam servindo pra muita coisa. Não estou dizendo que nunca é para usar variáveis intermediárias, apenas que para casos mais simples elas podem ser desnecessárias e o código fica mais claro e sucinto se não usá-las. Para um iniciante eu até entendo que fica mais didático criar variáveis para cada passo do algoritmo, mas conforme vc vai ganhando fluência na linguagem, começa a perceber que isso nem sempre é necessário.
Vale notar que não vi necessidade de outra função só para substituir a vírgula, já que é uma operação muito simples. Faria sentido ter essa função se ela fosse ser reusada em muitos lugares ou se a substituição fosse mais complexa, por exemplo. Mas neste caso não vi motivo que a justifique.
E também mudei para a função receber a mensagem já pronta, assim vc faz a formatação antes de chamá-la. Ou seja, quem for chamar a função cria a mensagem do jeito que quiser, tirando essa responsabilidade da função (afinal, a responsabilidade dela é de ler a entrada e converter para número; a formatação da mensagem não tem nada a ver com ela).
Por fim, não sei porque vc estava tratando IndexError
, pois não vi onde ele poderia ocorrer, por isso removi. Se quiser, também poderia acrescentar OverflowError
, que ocorre se o número digitado for maior que o limite do float
, fica a seu critério.
Ainda sobre variáveis desnecessárias, a função que calcula a média pode ser apenas:
def media(notas):
return sum(notas) / len(notas)
Novamente: sei que para iniciantes parece mais simples e didático ficar criando variáveis intermediárias. Mas eu ainda acho que neste caso só gera ruído desnecessário, pois o cálculo é tão simples que não justifica - neste caso, "menos é mais" :-)
Um lugar que acho que justifica uma variável é no cabeçalho, pois '=' * tamanho
é usado duas vezes, então poderia mudar para:
def cabecalho(msg):
tamanho = len(msg) + 30
borda = '=' * tamanho
print(f'{borda}\n{msg.center(tamanho, "-")}\n{borda}')
E também usei f-string para formatar (vi que vc usou em alguns lugares e em outros não, eu usei em todos os lugares, como veremos abaixo no código completo). Repare no \n
para pular a linha, em vez de usar vários print
's (fica a seu critério, pois dependendo da complexidade da mensagem, pode ficar mais claro fazer um print
para cada linha, como vc tinha feito).
Outra alternativa para imprimir seria:
print(f'{borda}\n{msg:-^{tamanho}}\n{borda}')
No caso, o ^
indica para centralizar o texto contido em msg
, o hífen antes dele é o caractere usado para o preenchimento, e em seguida informamos o tamanho. O resultado é o mesmo de usar center
, e para mais detalhes sobre os formatos aceitos, consulte a documentação.
Outro detalhe é que a função validadorDeNumeroInt
ficaria praticamente idêntica ao que eu fiz com validadorDeNumeroFloat
:
def validadorDeNumeroInt(msg):
while True:
try:
return int(input(msg))
except ValueError:
print('\033[31mERRO!! Valor invalido\033[m')
Então talvez vc possa criar uma função mais genérica:
def lerDados(msg, conversor):
"""Ler dados e converter para determinado tipo
Parâmetros:
msg -- mensagem do input
conversor -- função que converte os dados para outro tipo
"""
while True:
try:
return conversor(input(msg))
except ValueError:
print('\033[31mERRO!! Valor invalido\033[m')
E aí, basta usar um conversor diferente para cada caso:
# função que converte uma string em float, substituindo vírgula por ponto antes da conversão
def converterFloat(dados):
return float(dados.replace(',', '.'))
# se eu quiser ler um float
valor_float = lerDados('digite um float: ', converterFloat)
# se eu quiser ler um int
valor_int = lerDados('digite um int: ', int)
Desta forma fica bem flexível. Se precisar de alguma outra conversão mais complexa, ou simplesmente de uma regra diferente, basta criar outra função e passá-la para lerDados
.
Se eu quiser, por exemplo, que não aceite a vírgula, poderia usar lerDados(mensagem, float)
. E se eu quiser que tenha outras regras (por exemplo, que só aceite valores entre 1 e 100, etc), basta criar outra função que só aceite valores dentro destas regras.
E aqui eu acho que justifica uma função que faz o replace
e a conversão para float
, pois já é algo bem específico desta conversão e que ainda serve para ser reusado todas as vezes que lerDados
precisar fazer esta conversão específica.
Na validação do nome, veja na documentação que isaplha
retorna False
se a string for vazia, então não precisa da verificação nome != ''
. E como isalpha
já verifica a existência de espaços, se torna desnecessário chamar strip
depois (pois ali naquele ponto eu já sei que não tem espaços, então não precisa se preocupar em removê-los).
E no cadastro, por que a função cadastroAluno
usa a variável global alunos
, enquanto a função exibirAlunos
recebe a mesma como parâmetro? Eu diria que o ideal é que as duas recebam como parâmetro, assim ambas ficam mais genéricas, podendo funcionar para qualquer outro cadastro.
Outra coisa é que eu deixaria o from time import sleep
fora da função. Se estiver dentro: toda vez que a função for chamada, ele precisa verificar se o módulo já foi importado (para que não seja importado de novo), e embora seja relativamente rápido (ainda mais para um exercício que vai rodar poucas vezes), não vejo porque complicar. O próprio guia de estilo da linguagem recomenda que os import
's fiquem no início do arquivo (ou seja, fora de funções). O mesmo vale para import os
.
Já sobre limpar a tela, cls
funciona somente no Windows. Existem outras maneiras para funcionar em outros sistemas operacionais, dê uma olhada aqui.
Enfim, segue o código modificado abaixo. Também deixei alguns comentários com outras coisas que mudei:
import os
from time import sleep
def LimparTela(): # não mudei, mas como já disse, "cls" só funciona em Windows (veja o link indicado acima para opções em outros SO's)
os.system('cls')
def menu():
cabecalho('MENU')
print("""
[1] Adicionar alunos e notas
[2] Exibir alunos e notas
[3] Sair
""")
return lerDados('Digite uma das opções: ', int)
def cabecalho(msg):
tamanho = len(msg) + 30
borda = '=' * tamanho
print(f'{borda}\n{msg.center(tamanho, "-")}\n{borda}')
def converterFloat(dados):
return float(dados.replace(',', '.'))
def lerDados(msg, conversor):
while True:
try:
return conversor(input(msg))
except ValueError:
print('\033[31mERRO!! Valor invalido\033[m')
def validadorDeNome(msg):
while True:
nome = input(msg)
if nome.isalpha():
return nome
else:
print('\033[31mERRO!! Valor invalido\033[m')
def cadastroAluno(cadastros):
while True:
LimparTela()
cabecalho('CADASTRO DE ALUNOS')
notas = []
# para que declarar nome = "" se depois já vai atribuir outro valor?
nome = validadorDeNome('Digite o nome do aluno: ').capitalize() # não precisa de strip, pois isalpha já verifica se tem espaços
qtd_provas = lerDados(f'Quantas atividades/provas {nome} fez: ', int)
for num in range(1, qtd_provas + 1): # mudei os valores do range para não precisar somar 1 ao imprimir
# eliminei variável intermediária, pode jogar direto na lista
notas.append(lerDados(f'Digite a {num}ª nota: ', float))
cadastros[nome] = tuple(notas)
while True:# Loop infinito
try:
opc = input('Continuar adicionando alunos? [S/N] ').upper().strip()[0]
if opc in 'SN':
break
print('\033[31mERRO!! Valor invalido\033[m')
except IndexError:
print('\033[31mERRO!! Valor invalido\033[m')
if opc == 'N':
LimparTela()
break
def exibirAlunos(cadastros):
LimparTela()
cabecalho('BOLETIM')
if len(cadastros) > 0:
for nome, notas in cadastros.items(): # use nomes melhores em vez de "k" e "v"
print(f'O aluno {nome} teve as seguintes notas: ')
for i, nota in enumerate(notas, start=1): # começa o enumerate em 1, assim não precisa somar 1 ao imprimir
print(f'\t{i}ª nota: {nota:.1f}')
sleep(0.5)
print(f'Média final do aluno {nome} é {media(notas):.1f}')
print("==" * 20)
else:
print('Nenhum aluno foi cadastrado ainda')
while True:
if lerDados('Digite 999 para sair: ', int) == 999:
LimparTela()
break
print('\033[31mERRO!! Valor invalido\033[m')
def media(notas):
return sum(notas) / len(notas)
alunos = {'Rodrigo': (0, 3.5, 2, 1), 'Natan': (0, 8, 9, 10)}
while True:
opc = menu()
if opc == 1:
cadastroAluno(alunos)
elif opc == 2:
exibirAlunos(alunos)
elif opc == 3:
print('==' * 20)
print('Obrigado volte sempre!')
break
else:
LimparTela()
print('\033[31mDigite uma opção válida.\033[m')