最終プロジェクト:学生成績管理(パート 3)

前の 2 回のレッスンで、データ層とロジック層を構築しました。このレッスンではすべてを統合します——完全なコマンドラインメニューインターフェースを書きます。このレッスンを完了すれば、本当に使える学生成績管理システムが完成します。


完全なシステムコード

過去 3 回のレッスンのコードを 1 つのファイルに結合し、メニューインターフェースを追加します:

例:完全な成績管理システム

PYTHON
import json
import os
import statistics

# ====== 設定 ======
DATA_FILE = "grade_system.json"

DEFAULT_DATA = {
    "students": {},
    "subjects": ["Chinese", "Math", "English"],
    "classes": []
}

# ====== データ層 ======

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

def add_student(student_id, name, class_name):
    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": {}}
    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):
    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 add_score(student_id, subject, score):
    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}"

# ====== ロジック層 ======

def get_student_report(student_id):
    data = load_data()
    student = data["students"].get(student_id)
    if not student:
        return None
    report = {"name": student["name"], "class_name": student["class_name"],
              "scores": dict(student["scores"])}
    scores = list(student["scores"].values())
    if scores:
        report["average"] = round(sum(scores) / len(scores), 1)
        report["total"] = sum(scores)
        report["max_score"] = max(scores)
        report["min_score"] = min(scores)
    else:
        report["average"] = report["total"] = report["max_score"] = report["min_score"] = None
    return report

def get_class_ranking(class_name=None):
    data = load_data()
    results = []
    for sid, info in data["students"].items():
        if class_name and info["class_name"] != class_name:
            continue
        scores = list(info["scores"].values())
        total = sum(scores) if scores else 0
        avg = round(total / len(scores), 1) if scores else 0
        results.append({"id": sid, "name": info["name"], "class": info["class_name"],
                        "total": total, "avg": avg, "count": len(scores)})
    results.sort(key=lambda x: x["total"], reverse=True)
    for i, r in enumerate(results, 1):
        r["rank"] = i
    return results

def get_subject_stats():
    data = load_data()
    stats = {}
    for subject in data["subjects"]:
        scores = []
        for info in data["students"].values():
            if subject in info["scores"]:
                scores.append(info["scores"][subject])
        if scores:
            stats[subject] = {"avg": round(sum(scores) / len(scores), 1),
                              "max": max(scores), "min": min(scores), "count": len(scores)}
    return stats

# ====== プレゼンテーション層 ======

def show_main_menu():
    print("\n" + "=" * 40)
    print("Student Grade Management System")
    print("=" * 40)
    print("1. Student Management")
    print("2. Grade Management")
    print("3. Statistics and Reports")
    print("4. View Data")
    print("5. Exit")
    print("=" * 40)

def show_student_menu():
    print("\n--- Student Management ---")
    print("1. Add Student")
    print("2. Delete Student")
    print("3. Return to Main Menu")

def show_score_menu():
    print("\n--- Grade Management ---")
    print("1. Enter Grade")
    print("2. Return to Main Menu")

def show_stats_menu():
    print("\n--- Statistics and Reports ---")
    print("1. Individual Report Card")
    print("2. School-Wide Ranking")
    print("3. Subject Averages")
    print("4. Return to Main Menu")

def student_management():
    """学生管理サブメニュー"""
    while True:
        show_student_menu()
        choice = input("Enter choice: ").strip()
        
        if choice == "1":
            sid = input("Student ID: ").strip()
            name = input("Name: ").strip()
            cls = input("Class: ").strip()
            success, msg = add_student(sid, name, cls)
            print(f"  {'OK' if success else 'Error'}: {msg}")
        
        elif choice == "2":
            sid = input("Enter student ID to delete: ").strip()
            success, msg = delete_student(sid)
            print(f"  {'OK' if success else 'Error'}: {msg}")
        
        elif choice == "3":
            break
        
        else:
            print("Invalid choice!")

def score_management():
    """成績管理サブメニュー"""
    while True:
        show_score_menu()
        choice = input("Enter choice: ").strip()
        
        if choice == "1":
            sid = input("Student ID: ").strip()
            subject = input("Subject: ").strip()
            try:
                score = float(input("Score (0-100): ").strip())
            except ValueError:
                print("Error: Score must be a number!")
                continue
            success, msg = add_score(sid, subject, score)
            print(f"  {'OK' if success else 'Error'}: {msg}")
        
        elif choice == "2":
            break
        
        else:
            print("Invalid choice!")

def stats_query():
    """統計クエリサブメニュー"""
    while True:
        show_stats_menu()
        choice = input("Enter choice: ").strip()
        
        if choice == "1":
            sid = input("Enter student ID: ").strip()
            report = get_student_report(sid)
            if not report:
                print("Error: Student not found!")
                continue
            print(f"\nReport Card — {report['name']} ({report['class_name']})")
            print("-" * 30)
            for subject, score in report["scores"].items():
                print(f"  {subject}: {score}")
            if report["total"] is not None:
                print(f"  Total: {report['total']}")
                print(f"  Average: {report['average']}")
                print(f"  Highest: {report['max_score']}")
                print(f"  Lowest: {report['min_score']}")
            else:
                print("  (No grades yet)")
        
        elif choice == "2":
            ranking = get_class_ranking()
            if not ranking:
                print("No data!")
                continue
            print("\nSchool-Wide Ranking")
            print(f"{'Rank':<4}{'Name':<10}{'Class':<10}{'Total':<8}{'Avg':<6}")
            print("-" * 38)
            for r in ranking:
                print(f"{r['rank']:<4}{r['name']:<10}{r['class']:<10}{r['total']:<8}{r['avg']:<6}")
        
        elif choice == "3":
            stats = get_subject_stats()
            if not stats:
                print("No grade data!")
                continue
            print("\nSubject Statistics")
            print(f"{'Subject':<10}{'Average':<10}{'Max':<8}{'Min':<8}{'Count':<4}")
            print("-" * 38)
            for subject, info in stats.items():
                print(f"{subject:<10}{info['avg']:<10}{info['max']:<8}{info['min']:<8}{info['count']:<4}")
        
        elif choice == "4":
            break
        
        else:
            print("Invalid choice!")

def view_data():
    """生データを表示"""
    data = load_data()
    students = data["students"]
    if not students:
        print("No student data yet")
        return
    
    print(f"\nTotal: {len(students)} students")
    for sid, info in sorted(students.items()):
        scores = info["scores"]
        score_str = ", ".join(f"{s}:{v}" for s, v in scores.items()) if scores else "No grades"
        print(f"  {sid} {info['name']} ({info['class_name']}) -> {score_str}")

# ====== メインプログラム ======

def main():
    print("Welcome to the Student Grade Management System!")
    
    while True:
        show_main_menu()
        choice = input("Select option (1-5): ").strip()
        
        if choice == "1":
            student_management()
        elif choice == "2":
            score_management()
        elif choice == "3":
            stats_query()
        elif choice == "4":
            view_data()
        elif choice == "5":
            print("Thank you for using the system! Goodbye!")
            break
        else:
            print("Invalid choice. Please enter 1-5.")


if __name__ == "__main__":
    main()
▶ 試してみよう

プロジェクトサマリー

関数 責任
データ層 load/save_data JSON ファイル I/O
エンティティ層 add/delete_student 学生 CRUD
エンティティ層 add_score 成績 CRUD
ロジック層 get_student_report 個人成績表
ロジック層 get_class_ranking 順位計算
ロジック層 get_subject_stats 科目統計
プレゼンテーション層 main/main_menu メニューループ
プレゼンテーション層 student/score/stats_management サブメニュー

❓ よくある質問

Q コマンドメニューの while True ループは無限ループにならないのですか?
A なりません。各メニューには break を実行してループを終了するオプション(0 や特定の数字の入力)があります。各メニューに「終了」オプションがあれば安全です。
Q 曖昧検索には in キーワードで十分ですか?それとも正規表現が必要ですか?
A このプロジェクトでは keyword in name で十分です。より複雑な検索(先頭一致のみ、大文字小文字を区別しない、複数キーワードの組み合わせ)が必要な場合は、正規表現が必要になります——レッスン 31 で扱います。
Q プロジェクトを他の人が使えるようにパッケージ化するにはどうすればよいですか?
A 最も簡単な方法は .py ファイルを直接送ることです——Python がインストールされていれば誰でも実行できます。実行可能ファイル(.exe)を作成するには、PyInstaller を使用します:pip install pyinstaller && pyinstaller --onefile main.py

📖 まとめ


📝 練習問題

  1. 基本(難易度 ⭐):完全なシステムを実行し、3 人の学生とその成績を追加し、順位レポートを生成してください。

  2. 中級(難易度 ⭐⭐):システムに「レポートエクスポート」機能を追加してください。順位結果を ranking.csv ファイルにエクスポートします(csv モジュールを使用)。

  3. 上級(難易度 ⭐⭐⭐):上記のコードを参照せずに、ゼロから「書籍販売管理システム」を構築してください。機能:書籍追加(タイトル、著者、単価、在庫)、販売記録(書籍タイトル、数量→自動的に在庫を減らす)、在庫レポート表示(販売数順にソート)。データ永続化には JSON を使用します。

100%