Python: 综合项目:学生成绩管理(中)

第 34 课完成了数据层——学生和成绩的增删改查。这一课在数据层之上构建逻辑层:统计计算、排名、分数段分析等核心业务逻辑。第 36 课再加展示层(菜单界面),就组成完整的系统。


1. 逻辑层实现

这一层的代码依赖第 34 课的数据层函数(load_dataget_all_students 等)。

▶ 示例:成绩统计逻辑层

TEXT 📖 仅展示
# ====== 逻辑层:成绩统计分析 ======

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


2. 逻辑层测试

▶ 示例:逻辑层功能测试

TEXT 📖 仅展示
if __name__ == "__main__":
    print("=== 个人成绩单 ===")
    report = get_student_report("2024001")
    if report:
        print(f"姓名:{report['name']}")
        print(f"班级:{report['class_name']}")
        for subject, score in report["scores"].items():
            print(f"  {subject}:{score}")
        print(f"总分:{report['total']}")
        print(f"平均分:{report['average']}")
        print(f"最高:{report['max_score']}")
        print(f"最低:{report['min_score']}")

    print("\n=== 全班排名 ===")
    ranking = get_class_ranking()
    for r in ranking[:5]:
        print(f"第{r['rank']}名:{r['name']}({r['class_name']})总分 {r['total']}")

    print("\n=== 各科平均分 ===")
    averages = get_subject_averages()
    for subject, info in averages.items():
        if info:
            print(f"{subject}:平均 {info['average']},最高 {info['max']},最低 {info['min']}")

    print("\n=== 分数段分布 ===")
    dist = get_score_distribution()
    for label, count in dist.items():
        bar = "█" * count
        print(f"{label}:{bar} {count}人")
💡 注意: 上面的 import 假设数据层文件名为 grade_project_part1.py。如果你的文件名不同,修改 import 语句。最终完整系统中所有代码会合并到一个文件。



3. 异常处理设计

逻辑层中,对以下几种异常情况做了处理:

场景 返回值 说明
学生不存在 None 或空结果 调用方检查返回值
没有成绩数据 统计值为 None 调用方显示"暂无成绩"
空数据 空列表/空字典 不会崩溃,友好显示
非法数据 异常被捕获 用 try-except 保护

▶ 示例:班级平均分计算(难度⭐)

PYTHON
scores = {
    "2024001": {"name": "Alice", "class_name": "一班", "scores": {"语文": 85, "数学": 92, "英语": 88}},
    "2024002": {"name": "Bob", "class_name": "一班", "scores": {"语文": 78, "数学": 85, "英语": 90}},
    "2024003": {"name": "Charlie", "class_name": "二班", "scores": {"语文": 92, "数学": 88, "英语": 95}},
    "2024004": {"name": "Diana", "class_name": "二班", "scores": {"语文": 70, "数学": 75, "英语": 80}},
}

def class_averages(students):
    """按班级计算各科平均分"""
    classes = {}
    for sid, info in students.items():
        cls = info["class_name"]
        if cls not in classes:
            classes[cls] = {}
        for subject, score in info["scores"].items():
            if subject not in classes[cls]:
                classes[cls][subject] = []
            classes[cls][subject].append(score)

    result = {}
    for cls, subjects in classes.items():
        result[cls] = {}
        for subject, vals in subjects.items():
            result[cls][subject] = round(sum(vals) / len(vals), 1)

    return result

averages = class_averages(scores)
for cls, subjects in averages.items():
    print(f"\n{cls}:")
    for subject, avg in subjects.items():
        print(f"  {subject}平均分:{avg}")
▶ 试一试

输出:

TEXT 📖 仅展示
一班:
  语文平均分:81.5
  数学平均分:88.5
  英语平均分:89.0

二班:
  语文平均分:81.0
  数学平均分:81.5
  英语平均分:87.5

❓ 常见问题

Q 排序时 key=lambda 怎么理解?
A key 参数指定排序的依据。lambda item: item[1] 表示"取每个元素的第二项(总分)作为排序键"。等价于 def get_score(item): return item[1],lambda 只是简写。
Q 统计函数返回 None 而不是 0,调用方不会麻烦吗?
A 返回 None 能区分"成绩不存在"和"成绩确实是 0 分"——这是有意义的区别。调用方用 if result is not None: 判断即可,比误把"没数据"当"0分"更安全。

❓ 常见问题

Q 这个概念和 XXX 有什么区别?
A 简洁对比两者的核心差异和使用场景。

📖 小节


📝 作业

  1. 基础题(难度⭐):调用 get_student_report() 查看一个学生的完整成绩单,确认输出正确。

  2. 进阶题(难度⭐⭐):给逻辑层增加一个 get_top_n(n) 函数,返回全校总分前 N 名的学生。

  3. 挑战题(难度⭐⭐⭐):给逻辑层增加 get_subject_pass_rate(subject, pass_score=60) 函数,计算某一科的及格率(成绩 >= pass_score 的人数 / 总人数)。再计算各科及格率并输出对比。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

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

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