最終プロジェクト:学生成績管理(パート 2)
レッスン 34 ではデータ層を完成させました——学生と成績の CRUD 操作です。このレッスンでは、データ層の上にロジック層を構築します:統計計算、順位付け、スコア分布分析、その他の中核ビジネスロジックです。レッスン 36 ではプレゼンテーション層(メニューインターフェース)を追加してシステムを完成させます。
ロジック層の実装
この層のコードは、レッスン 34 のデータ層関数(load_data、get_all_students など)に依存します。
例:成績統計ロジック層
PYTHON
# ====== ロジック層:成績統計と分析 ======
import statistics
from grade_project_part1 import load_data, get_all_students
def calculate_student_average(student_id):
"""単一の学生の平均スコアを計算"""
data = load_data()
student = data["students"].get(student_id)
if not student:
return None
scores = student["scores"].values()
if not scores:
return None
return sum(scores) / len(scores)
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["max_score"] = max(scores)
report["min_score"] = min(scores)
report["total"] = sum(scores)
else:
report["average"] = None
report["max_score"] = None
report["min_score"] = None
report["total"] = 0
return report
def get_class_ranking(class_name=None):
"""クラス順位を取得(合計スコア降順)"""
data = load_data()
students = data["students"]
# すべての学生(または特定のクラス)を収集
results = []
for sid, info in 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({
"student_id": sid,
"name": info["name"],
"class_name": info["class_name"],
"total": total,
"average": avg,
"score_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_averages():
"""各科目の平均スコアを計算"""
data = load_data()
subjects = data["subjects"]
students = data["students"]
result = {}
for subject in subjects:
scores = []
for info in students.values():
if subject in info["scores"]:
scores.append(info["scores"][subject])
if scores:
result[subject] = {
"average": round(sum(scores) / len(scores), 1),
"max": max(scores),
"min": min(scores),
"count": len(scores)
}
else:
result[subject] = None
return result
def get_score_distribution(subject=None):
"""スコア分布を範囲別に計算"""
data = load_data()
students = data["students"]
ranges = [
("90-100", 90, 101),
("80-89", 80, 90),
("70-79", 70, 80),
("60-69", 60, 70),
("0-59", 0, 60),
]
if subject:
# 特定科目の分布をカウント
result = {label: 0 for label, _, _ in ranges}
for info in students.values():
if subject in info["scores"]:
score = info["scores"][subject]
for label, low, high in ranges:
if low <= score < high:
result[label] += 1
break
return result
else:
# 全科目を合わせた分布をカウント
result = {label: 0 for label, _, _ in ranges}
for info in students.values():
for score in info["scores"].values():
for label, low, high in ranges:
if low <= score < high:
result[label] += 1
break
return result
def get_students_by_class():
"""学生をクラス別にグループ化"""
data = load_data()
students = data["students"]
classes = {}
for sid, info in students.items():
cls = info["class_name"]
if cls not in classes:
classes[cls] = []
classes[cls].append({
"student_id": sid,
"name": info["name"],
"score_count": len(info["scores"])
})
return classes
ロジック層のテスト
例:ロジック層の機能テスト
PYTHON
if __name__ == "__main__":
print("=== Individual Report Card ===")
report = get_student_report("2024001")
if report:
print(f"Name: {report['name']}")
print(f"Class: {report['class_name']}")
for subject, score in report["scores"].items():
print(f" {subject}: {score}")
print(f"Total: {report['total']}")
print(f"Average: {report['average']}")
print(f"Max: {report['max_score']}")
print(f"Min: {report['min_score']}")
print("\n=== Class Ranking ===")
ranking = get_class_ranking()
for r in ranking[:5]:
print(f"#{r['rank']}: {r['name']} ({r['class_name']}) Total {r['total']}")
print("\n=== Subject Averages ===")
averages = get_subject_averages()
for subject, info in averages.items():
if info:
print(f"{subject}: Avg {info['average']}, Max {info['max']}, Min {info['min']}")
print("\n=== Score Distribution ===")
dist = get_score_distribution()
for label, count in dist.items():
bar = "#" * count
print(f"{label}: {bar} {count} students")
補足: 上の
importはデータ層ファイルがgrade_project_part1.pyという名前であることを想定しています。ファイル名が異なる場合は import 文を修正してください。最終的な完全システムでは、すべてのコードが 1 つのファイルに統合されます。
例外処理の設計
ロジック層では以下の例外的なケースを処理します:
| シナリオ | 戻り値 | 説明 |
|---|---|---|
| 学生が存在しない | None または空の結果 |
呼び出し側が戻り値をチェック |
| 成績データがない | 統計値が None |
呼び出し側が「まだ成績がありません」と表示 |
| 空のデータ | 空のリスト/空の辞書 | クラッシュせず、親しみやすいメッセージを表示 |
| 無効なデータ | 例外をキャッチ | try-except で保護 |
❓ よくある質問
Q ソートの
key=lambda を理解するにはどうすればよいですか?A
key パラメータはソート基準を指定します。lambda item: item[1] は「各アイテムの 2 番目の要素をソートキーとして取得する」ことを意味します。これは def get_score(item): return item[1] と同等です——lambda は単なる省略形です。Q 0 ではなく
None を返すと、呼び出し側が扱いにくくなりませんか?A
None を返すことで「スコアが存在しない」と「スコアが実際に 0」を区別できます——これは意味のある違いです。呼び出し側は if result is not None: で確認でき、「データなし」を誤って「0 点」と扱うよりも安全です。📖 まとめ
- ロジック層はデータ層の上に位置し、「保存」ではなく「計算」に焦点を当てる
- 個人成績表:科目別スコア、合計、平均、最大/最小を表示
- クラス順位:合計スコア降順でソート、自動生成された順位番号
- 科目平均:全学生のスコアを反復し、科目別に集計
- スコア分布:各スコア範囲の学生数をカウント
- 例外処理:学生/スコアが存在しない場合、エラーにする代わりに
Noneを返す
📝 練習問題
-
基本(難易度 ⭐):
get_student_report()を呼び出して 1 人の学生の完全な成績表を表示し、出力が正しいことを確認してください。 -
中級(難易度 ⭐⭐):ロジック層に
get_top_n(n)関数を追加してください。学校全体で合計スコアが上位 N 位までの学生を返します。 -
上級(難易度 ⭐⭐⭐):ロジック層に
get_subject_pass_rate(subject, pass_score=60)関数を追加してください。指定された科目の合格率(スコア >= pass_score の学生数/全学生数)を計算します。その後、全科目の合格率を計算して比較してください。



