Executando verificação de segurança...
13

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')
Carregando publicação patrocinada...
4
2
1

Parabéns @RodrigoPechini e parabéns à @kht pelo feedback completão, gostaria de ter feedbacks assim nos meus códigos. Também vou salvar pra terminar de ler o feedback, com certeza tem muitas dicas maravilhosas