Python: Projeto: Notas Escolares (Pt.1)

Este é o projeto final do tutorial de Python. Em três lições, vamos construir um sistema completo de gerenciamento de notas escolares do zero. O projeto tem três camadas: camada de dados (persistência JSON), camada de lógica (processamento de notas) e camada de apresentação (interface de linha de comando). Esta lição constrói a base — requisitos e a camada de dados.


1. Visão Geral do Projeto

TEXT 📖 Somente leitura
Sistema de Gerenciamento de Notas Escolares
├── Lição 34: Camada de Dados — Análise de requisitos + Persistência JSON/CSV
├── Lição 35: Camada de Lógica — Estatísticas de notas + Ordenação + Tratamento de exceções
└── Lição 36: Camada de Apresentação — Interface de menu + Busca + Relatórios


2. Especificação de Requisitos

TEXT 📖 Somente leitura
1. Gerenciamento de Alunos
   ├── Adicionar aluno (nome, matrícula, turma)
   └── Remover aluno (por matrícula)

2. Gerenciamento de Notas
   ├── Inserir notas (por matrícula para múltiplas disciplinas)
   ├── Modificar notas
   └── Remover registros de notas

3. Estatísticas e Análise
   ├── Boletim individual
   ├── Ranking da turma
   ├── Médias por disciplina
   └── Distribuição de notas

4. Persistência de Dados
   ├── Armazenar dados em formato JSON em arquivo
   └── Carregamento automático na inicialização


3. Design da Estrutura de Dados

▶ Exemplo: Estrutura de Dados JSON

PYTHON
# Dados armazenados em formato JSON
{
    "students": {
        "2024001": {
            "name": "João Silva",
            "class_name": "Turma 1",
            "scores": {
                "Português": 85,
                "Matemática": 92,
                "Inglês": 78
            }
        },
        "2024002": {
            "name": "Maria Souza",
            "class_name": "Turma 1",
            "scores": {
                "Português": 90,
                "Matemática": 88,
                "Inglês": 95
            }
        }
    },
    "subjects": ["Português", "Matemática", "Inglês"],
    "classes": ["Turma 1", "Turma 2"]
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
# Executed successfully
💡 Por que usar a matrícula como chave? Usar a matrícula (string) como chave do dicionário torna as buscas O(1) — quer haja 10 ou 10.000 alunos, a velocidade de busca é a mesma.



4. Implementação da Camada de Dados

▶ Exemplo: Implementação e Teste da Camada de Dados

PYTHON 📖 Somente leitura
import json
import os

DATA_FILE = "grade_system.json"

# ====== Estrutura de Dados Padrão ======
DEFAULT_DATA = {
    "students": {},
    "subjects": ["Português", "Matemática", "Inglês"],
    "classes": []
}


# ====== Camada de Dados ======

def load_data():
    """Carrega dados do arquivo JSON"""
    if not os.path.exists(DATA_FILE):
        save_data(DEFAULT_DATA.copy())
        return DEFAULT_DATA.copy()
    with open(DATA_FILE, "r", encoding="utf-8") as f:
        return json.load(f)


def save_data(data):
    """Salva dados no arquivo JSON"""
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)


# ====== Gerenciamento de Alunos ======

def add_student(student_id, name, class_name):
    """Adiciona um aluno"""
    data = load_data()

    if student_id in data["students"]:
        return False, f"Matrícula {student_id} já existe!"

    data["students"][student_id] = {
        "name": name,
        "class_name": class_name,
        "scores": {}
    }

    # Atualiza lista de turmas
    if class_name not in data["classes"]:
        data["classes"].append(class_name)

    save_data(data)
    return True, f"Aluno adicionado: {name} ({student_id})"


def delete_student(student_id):
    """Remove um aluno"""
    data = load_data()

    if student_id not in data["students"]:
        return False, f"Matrícula {student_id} não existe!"

    name = data["students"][student_id]["name"]
    del data["students"][student_id]
    save_data(data)
    return True, f"Aluno removido: {name}"


def get_student(student_id):
    """Obtém informações de um aluno"""
    data = load_data()
    return data["students"].get(student_id)


def get_all_students():
    """Obtém todos os alunos"""
    data = load_data()
    return data["students"]


# ====== Gerenciamento de Notas ======

def add_score(student_id, subject, score):
    """Insere uma nota"""
    data = load_data()

    if student_id not in data["students"]:
        return False, f"Matrícula {student_id} não existe!"

    if subject not in data["subjects"]:
        return False, f"Disciplina {subject} não existe!"

    if not isinstance(score, (int, float)) or score < 0 or score > 100:
        return False, "A nota deve estar entre 0 e 100!"

    data["students"][student_id]["scores"][subject] = score
    save_data(data)
    return True, f"Inserida nota de {data['students'][student_id]['name']} em {subject}: {score}"


def update_score(student_id, subject, score):
    """Atualiza uma nota (mesma function que add_score)"""
    return add_score(student_id, subject, score)


def delete_score(student_id, subject):
    """Remove a nota de uma disciplina"""
    data = load_data()

    if student_id not in data["students"]:
        return False, f"Matrícula {student_id} não existe!"

    if subject not in data["students"][student_id]["scores"]:
        return False, f"{data['students'][student_id]['name']} não tem nota em {subject}"

    del data["students"][student_id]["scores"][subject]
    save_data(data)
    return True, f"Nota de {data['students'][student_id]['name']} em {subject} removida"


# ====== Código de Teste ======
if __name__ == "__main__":
    print("=== Testando Camada de Dados ===")
    print(add_student("2024001", "João Silva", "Turma 1"))
    print(add_student("2024002", "Maria Souza", "Turma 1"))
    print(add_student("2024003", "Pedro Santos", "Turma 2"))

    print(add_score("2024001", "Português", 85))
    print(add_score("2024001", "Matemática", 92))
    print(add_score("2024002", "Português", 90))

    student = get_student("2024001")
    print(f"\nInformações de João Silva: {student}")

    print(f"\nTotal de alunos: {len(get_all_students())}")
86 linhas de lógica (limite de 40, somente leitura)

Saída:

TEXT 📖 Somente leitura
=== Testando Camada de Dados ===

▶ Exemplo: Operações com Dados de Alunos (Dificuldade ⭐)

PYTHON
students = {}

def add_student(students, sid, name, class_name):
    if sid in students:
        return f"Matrícula {sid} já existe!"
    students[sid] = {"name": name, "class_name": class_name, "scores": {}}
    return f"Adicionado: {name} ({sid})"

def add_score(students, sid, subject, score):
    if sid not in students:
        return f"Matrícula {sid} não existe!"
    if score < 0 or score > 100:
        return "Nota deve estar entre 0 e 100!"
    students[sid]["scores"][subject] = score
    return f"Registrado {subject} de {students[sid]['name']}: {score}"

def get_average(students, sid):
    if sid not in students or not students[sid]["scores"]:
        return None
    scores = students[sid]["scores"].values()
    return sum(scores) / len(scores)

print(add_student(students, "001", "Alice", "Turma 1"))
print(add_student(students, "002", "Bob", "Turma 1"))
print(add_score(students, "001", "Português", 85))
print(add_score(students, "001", "Matemática", 92))
print(add_score(students, "002", "Português", 78))

avg = get_average(students, "001")
print(f"\nMédia de Alice: {avg:.1f}")
print(f"Nota de Português do Bob: {students['002']['scores']['Português']}")
print(f"Total de alunos: {len(students)}")
▶ Experimente

Saída:

TEXT 📖 Somente leitura
Adicionado: Alice (001)
Adicionado: Bob (002)
Registrado Português de Alice: 85
Registrado Matemática de Alice: 92
Registrado Português de Bob: 78

Média de Alice: 88.5
Nota de Português do Bob: 78
Total de alunos: 2


5. Destaques de Design


❓ Perguntas Frequentes

P: Por que usar a matrícula como chave do dicionário em vez do nome? R: Nomes podem se repetir (alunos com o mesmo nome), mas matrículas são identificadores únicos. Chaves de dicionário devem ser únicas; usar matrículas garante velocidade de busca O(1) e sem conflitos. P: É uma boa prática usar caminho de arquivo JSON fixo no código? R: Por simplicidade, o tutorial usa caminho fixo. Em projetos reais, use arquivos de configuração ou argumentos de linha de comando para especificar o caminho. Use os.path.join() para construir caminhos e evite valores fixos. P: Por que retornar uma tupla (sucesso, mensagem) em vez de levantar exceções? R: Este é o "padrão de código de status" — o chamador pode decidir o que fazer com base em sucesso, que é mais leve que try/except. É adequado para pequenas ferramentas CLI; para projetos maiores, classes de exceção personalizadas são recomendadas.


📖 Resumo



📝 Exercícios

  1. Iniciante (Dificuldade ⭐): Execute o código da camada de dados acima, adicione 2-3 alunos com suas notas e verifique se o arquivo JSON foi gerado corretamente.

  2. Intermediário (Dificuldade ⭐⭐): Adicione uma função update_student(student_id, name=None, class_name=None) à camada de dados que atualiza apenas os campos fornecidos (deixando outros inalterados).

  3. Avançado (Dificuldade ⭐⭐⭐): Adicione funcionalidade de inserção em lote a add_score — aceite um dicionário como {"Português": 85, "Matemática": 92} para inserir múltiplas notas de uma vez. Requisito: se qualquer nota for inválida, reverta toda a operação em lote (não salve nenhum dado).

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%