Final Project: Student Grade Management (Part 1)

This is the capstone project of the Python tutorial. Over three lessons, we will build a complete student grade management system from scratch. The project has three layers: data layer (JSON persistence), logic layer (grade processing), and presentation layer (command-line interface). This lesson builds the foundation — requirements and the data layer.


Project Overview

TEXT
Student Grade Management System
├── Lesson 34: Data Layer — Requirements analysis + JSON/CSV persistence
├── Lesson 35: Logic Layer — Grade statistics + Sorting + Exception handling
└── Lesson 36: Presentation Layer — Menu interface + Search + Reports

Requirements Specification

TEXT
1. Student Management
   ├── Add student (name, student ID, class)
   └── Delete student (by student ID)

2. Grade Management
   ├── Enter grades (by student ID for multiple subjects)
   ├── Modify grades
   └── Delete grade records

3. Statistics and Analysis
   ├── Individual report card
   ├── Class ranking
   ├── Subject averages
   └── Score distribution

4. Data Persistence
   ├── Store data in JSON format to a file
   └── Auto-load on startup

Data Structure Design

Example: JSON Data Structure

PYTHON
# Data stored in JSON format
{
    "students": {
        "2024001": {
            "name": "Zhang San",
            "class_name": "Class 1",
            "scores": {
                "Chinese": 85,
                "Math": 92,
                "English": 78
            }
        },
        "2024002": {
            "name": "Li Si",
            "class_name": "Class 1",
            "scores": {
                "Chinese": 90,
                "Math": 88,
                "English": 95
            }
        }
    },
    "subjects": ["Chinese", "Math", "English"],
    "classes": ["Class 1", "Class 2"]
}
▶ Try it Yourself

Tip: Why use student ID as the key? Using student ID (string) as the dictionary key makes lookups O(1) — whether there are 10 or 10,000 students, the lookup speed is the same.


Data Layer Implementation

Example: Data Layer Implementation and Testing

PYTHON
import json
import os

DATA_FILE = "grade_system.json"

# ====== Default Data Structure ======
DEFAULT_DATA = {
    "students": {},
    "subjects": ["Chinese", "Math", "English"],
    "classes": []
}


# ====== Data Layer ======

def load_data():
    """Load data from JSON file"""
    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):
    """Save data to JSON file"""
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)


# ====== Student Management ======

def add_student(student_id, name, class_name):
    """Add a student"""
    data = load_data()

    if student_id in data["students"]:
        return False, f"Student ID {student_id} already exists!"

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

    # Update class list
    if class_name not in data["classes"]:
        data["classes"].append(class_name)

    save_data(data)
    return True, f"Added student: {name} ({student_id})"


def delete_student(student_id):
    """Delete a student"""
    data = load_data()

    if student_id not in data["students"]:
        return False, f"Student ID {student_id} does not exist!"

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


def get_student(student_id):
    """Get a single student's info"""
    data = load_data()
    return data["students"].get(student_id)


def get_all_students():
    """Get all students"""
    data = load_data()
    return data["students"]


# ====== Grade Management ======

def add_score(student_id, subject, score):
    """Enter a grade"""
    data = load_data()

    if student_id not in data["students"]:
        return False, f"Student ID {student_id} does not exist!"

    if subject not in data["subjects"]:
        return False, f"Subject {subject} does not exist!"

    if not isinstance(score, (int, float)) or score < 0 or score > 100:
        return False, "Score must be between 0 and 100!"

    data["students"][student_id]["scores"][subject] = score
    save_data(data)
    return True, f"Entered {data['students'][student_id]['name']}'s {subject} score: {score}"


def update_score(student_id, subject, score):
    """Update a grade (same function as add_score)"""
    return add_score(student_id, subject, score)


def delete_score(student_id, subject):
    """Delete a grade for a subject"""
    data = load_data()

    if student_id not in data["students"]:
        return False, f"Student ID {student_id} does not exist!"

    if subject not in data["students"][student_id]["scores"]:
        return False, f"{data['students'][student_id]['name']} has no {subject} score"

    del data["students"][student_id]["scores"][subject]
    save_data(data)
    return True, f"Deleted {data['students'][student_id]['name']}'s {subject} score"


# ====== Test Code ======
if __name__ == "__main__":
    print("=== Testing Data Layer ===")
    print(add_student("2024001", "Zhang San", "Class 1"))
    print(add_student("2024002", "Li Si", "Class 1"))
    print(add_student("2024003", "Wang Wu", "Class 2"))

    print(add_score("2024001", "Chinese", 85))
    print(add_score("2024001", "Math", 92))
    print(add_score("2024002", "Chinese", 90))

    student = get_student("2024001")
    print(f"\nZhang San's info: {student}")

    print(f"\nTotal students: {len(get_all_students())}")
▶ Try it Yourself

Design Highlights


FAQ

Q Why use student ID as the dictionary key instead of name?
A Names can duplicate (students with the same name), but student IDs are unique identifiers. Dictionary keys must be unique; using student IDs guarantees O(1) lookup speed and no conflicts.
Q Is it good practice to hardcode the JSON file path?
A For simplicity, the tutorial hardcodes the path. In real projects, use configuration files or command-line arguments to specify the path. Use os.path.join() to construct paths and avoid hardcoding.
Q Why return a (success, message) tuple instead of raising exceptions?
A This is the "status code pattern" — the caller can decide what to do based on success, which is lighter than try/except. It's suitable for small CLI tools; for larger projects, custom exception classes are recommended.

Summary


Exercises

  1. Beginner (Difficulty: Star): Run the data layer code above, add 2-3 students with their grades, and verify the JSON file is generated correctly.

  2. Intermediate (Difficulty: Star-Star): Add an update_student(student_id, name=None, class_name=None) function to the data layer that only updates the provided fields (leaving others unchanged).

  3. Advanced (Difficulty: Star-Star-Star): Add batch entry functionality to add_score — accept a dictionary like {"Chinese": 85, "Math": 92} to enter multiple grades at once. Requirement: if any score is invalid, roll back the entire batch operation (don't save any data).

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏