Projeto Final: Gerenciamento de Notas Escolares (Parte 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.
Visão Geral do Projeto
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
Especificação de Requisitos
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
Design da Estrutura de Dados
Exemplo: Estrutura de Dados JSON
# 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"]
}
Implementação da Camada de Dados
Exemplo: Implementação e Teste da Camada de Dados
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 função 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())}")
Destaques de Design
- Retorno uniforme de funções: Cada função retorna uma tupla
(bool, str)—Truesignifica sucesso,Falsesignifica falha, e a string é a mensagem. - Operações repetidas encapsuladas: As funções
load_data()esave_data()são reutilizadas em todas as operações de dados. - Validação de entrada: Verificação de faixa de notas e existência de matrícula são feitas na camada de dados, para que chamadores de camadas superiores não precisem repeti-las.
- Pronto para expansão: As Lições 35 e 36 adicionarão funcionalidades sobre esta camada de dados.
❓ 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 emsucesso, que é mais leve que try/except. É adequado para pequenas ferramentas CLI; para projetos maiores, classes de exceção personalizadas são recomendadas.
📖 Resumo
- O projeto usa arquitetura de três camadas: Camada de Dados -> Camada de Lógica -> Camada de Apresentação; esta lição completa a camada de dados
- Os dados são persistidos em formato JSON; matrículas servem como chaves do dicionário para busca O(1)
- As notas variam de 0 a 100, validadas na entrada
- Todas as funções de operação retornam tuplas
(sucesso, mensagem)para fácil manipulação pelo chamador
📝 Atividades
-
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.
-
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). -
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).



