最終プロジェクト:学生成績管理(パート 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。📖 まとめ
- 3 層アーキテクチャ:データ層 → ロジック層 → プレゼンテーション層。各層の責任は明確
- データ層:JSON ファイルの読み書きのカプセル化、基本的な CRUD 操作
- ロジック層:成績統計、順位付け、分析のビジネスルール
- プレゼンテーション層:メニューインターフェース、入出力、ユーザー対話
- 例外処理:入力検証、空データ処理、ファイル未発見処理
- プロジェクトは約 200 行のコアコードで、完全な学生成績管理システムを実装
📝 練習問題
-
基本(難易度 ⭐):完全なシステムを実行し、3 人の学生とその成績を追加し、順位レポートを生成してください。
-
中級(難易度 ⭐⭐):システムに「レポートエクスポート」機能を追加してください。順位結果を
ranking.csvファイルにエクスポートします(csvモジュールを使用)。 -
上級(難易度 ⭐⭐⭐):上記のコードを参照せずに、ゼロから「書籍販売管理システム」を構築してください。機能:書籍追加(タイトル、著者、単価、在庫)、販売記録(書籍タイトル、数量→自動的に在庫を減らす)、在庫レポート表示(販売数順にソート)。データ永続化には JSON を使用します。



