🐍 学生管理系统 — 第一阶段毕业项目
🕐 预计用时:4-5 小时 | 🎯 目标:用 OOP 架构完成完整的学生管理系统
📖 今日目录
1. 项目架构设计
student_system/
├── main.py ← 主程序入口
├── models.py ← 数据模型(Student, Course)
├── manager.py ← 业务逻辑(GradeManager)
├── storage.py ← 文件存储(FileManager)
├── ui.py ← 菜单界面(MenuUI)
└── students.json ← 数据文件(自动生成)
📋 功能清单
🏗️ 类关系图
┌─────────────┐ ┌──────────────┐
│ Student │────▶│ Course │ (1对多)
│ - sid │ │ - name │
│ - name │ │ - score │
│ - age │ └──────────────┘
│ - courses │
└─────────────┘
▲
│ 管理
┌──────────────┐ ┌──────────────┐
│ GradeManager │────▶│ FileManager │ (依赖)
│ - students │ │ - save() │
│ - add() │ │ - load() │
│ - delete() │ └──────────────┘
│ - search() │
│ - ranking() │
└──────────────┘
▲
│ 调用
┌──────────────┐
│ MenuUI │
│ - run() │
└──────────────┘
2. Student 学生类 (models.py)
from functools import total_ordering
from datetime import datetime
@total_ordering
class Student:
"""学生类"""
_id_counter = 0
def __init__(self, name, sid, age, courses=None):
Student._id_counter += 1
self.name = name
self.sid = sid # 学号
self.age = age
self.courses = courses or [] # Course 对象列表
self.created_at = datetime.now().strftime("%Y-%m-%d %H:%M")
@property
def total_score(self):
"""总分"""
return sum(c.score for c in self.courses)
@property
def avg_score(self):
"""平均分"""
return self.total_score / len(self.courses) if self.courses else 0
@property
def course_count(self):
"""课程数"""
return len(self.courses)
@property
def passed_all(self):
"""是否全部及格"""
return all(c.score >= 60 for c in self.courses) if self.courses else False
@property
def failed_courses(self):
"""不及格课程"""
return [c for c in self.courses if c.score < 60]
def add_course(self, course):
"""添加课程(如果已存在则更新)"""
for i, c in enumerate(self.courses):
if c.name == course.name:
self.courses[i] = course
return
self.courses.append(course)
def remove_course(self, course_name):
"""删除课程"""
self.courses = [c for c in self.courses if c.name != course_name]
def get_course(self, course_name):
"""获取课程"""
for c in self.courses:
if c.name == course_name:
return c
return None
def __eq__(self, other):
if not isinstance(other, Student):
return NotImplemented
return self.sid == other.sid
def __lt__(self, other):
if not isinstance(other, Student):
return NotImplemented
return self.total_score < other.total_score
def __str__(self):
status = "✅ 全部及格" if self.passed_all else f"❌ {len(self.failed_courses)}科不及格"
return (f"[{self.sid}] {self.name}, {self.age}岁, "
f"{self.course_count}科, 均分{self.avg_score:.1f}, {status}")
def to_dict(self):
"""转为字典(用于 JSON 序列化)"""
return {
"name": self.name,
"sid": self.sid,
"age": self.age,
"courses": [c.to_dict() for c in self.courses],
"created_at": self.created_at,
}
@classmethod
def from_dict(cls, data):
"""从字典创建(用于 JSON 反序列化)"""
from models import Course
courses = [Course.from_dict(c) for c in data.get("courses", [])]
s = cls(data["name"], data["sid"], data["age"], courses)
s.created_at = data.get("created_at", "")
return s
class Course:
"""课程类"""
def __init__(self, name, score, credit=1.0):
self.name = name
self.score = score
self.credit = credit
@property
def grade(self):
"""等级"""
if self.score >= 90: return "A"
if self.score >= 80: return "B"
if self.score >= 70: return "C"
if self.score >= 60: return "D"
return "F"
@property
def passed(self):
return self.score >= 60
def __str__(self):
status = "✅" if self.passed else "❌"
return f"{self.name}: {self.score}分 ({self.grade}) {status}"
def __eq__(self, other):
return isinstance(other, Course) and self.name == other.name
def to_dict(self):
return {"name": self.name, "score": self.score, "credit": self.credit}
@classmethod
def from_dict(cls, data):
return cls(data["name"], data["score"], data.get("credit", 1.0))
3. GradeManager 成绩管理器 (manager.py)
from models import Student, Course
class GradeManager:
"""成绩管理器:核心业务逻辑"""
def __init__(self):
self.students = {} # {sid: Student}
# ============ 增删改查 ============
def add_student(self, name, sid, age):
"""添加学生"""
if sid in self.students:
raise ValueError(f"学号 {sid} 已存在")
student = Student(name, sid, age)
self.students[sid] = student
return student
def remove_student(self, sid):
"""删除学生"""
if sid not in self.students:
raise KeyError(f"学号 {sid} 不存在")
return self.students.pop(sid)
def get_student(self, sid):
"""获取学生"""
if sid not in self.students:
raise KeyError(f"学号 {sid} 不存在")
return self.students[sid]
def update_student(self, sid, **kwargs):
"""更新学生信息"""
student = self.get_student(sid)
for key, value in kwargs.items():
if hasattr(student, key) and key != "sid":
setattr(student, key, value)
return student
def add_grade(self, sid, course_name, score, credit=1.0):
"""添加/更新成绩"""
student = self.get_student(sid)
course = Course(course_name, score, credit)
student.add_course(course)
return course
def remove_grade(self, sid, course_name):
"""删除成绩"""
student = self.get_student(sid)
student.remove_course(course_name)
# ============ 查询 ============
def search_by_name(self, keyword):
"""按姓名搜索"""
keyword = keyword.lower()
return [s for s in self.students.values() if keyword in s.name.lower()]
def search_by_sid(self, keyword):
"""按学号搜索"""
return [s for s in self.students.values() if keyword in s.sid]
def search(self, keyword):
"""综合搜索"""
return self.search_by_name(keyword) + [
s for s in self.search_by_sid(keyword) if s not in self.search_by_name(keyword)
]
def get_all_students(self):
"""获取所有学生"""
return list(self.students.values())
# ============ 排名 ============
def ranking(self, reverse=True):
"""按总分排名"""
return sorted(self.students.values(), reverse=reverse)
def course_ranking(self, course_name):
"""某科排名"""
students_with_course = [
s for s in self.students.values() if s.get_course(course_name)
]
return sorted(students_with_course,
key=lambda s: s.get_course(course_name).score, reverse=True)
# ============ 统计 ============
def course_stats(self, course_name):
"""某科统计"""
scores = []
for s in self.students.values():
c = s.get_course(course_name)
if c:
scores.append(c.score)
if not scores:
return None
return {
"count": len(scores),
"avg": sum(scores) / len(scores),
"max": max(scores),
"min": min(scores),
"pass_rate": len([s for s in scores if s >= 60]) / len(scores) * 100,
}
def overall_stats(self):
"""整体统计"""
students = list(self.students.values())
if not students:
return None
all_courses = set()
for s in students:
for c in s.courses:
all_courses.add(c.name)
return {
"total_students": len(students),
"total_courses": len(all_courses),
"course_list": sorted(all_courses),
"avg_score": sum(s.avg_score for s in students) / len(students),
"all_passed": len([s for s in students if s.passed_all]),
}
def __len__(self):
return len(self.students)
4. FileManager 文件管理 (storage.py)
import json
import csv
import os
from models import Student
class FileManager:
"""文件管理器:JSON 持久化 + CSV 导入导出"""
def __init__(self, filename="students.json"):
self.filename = filename
def save(self, manager):
"""保存到 JSON"""
data = {
"version": "1.0",
"count": len(manager),
"students": {sid: s.to_dict() for sid, s in manager.students.items()}
}
with open(self.filename, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
print(f"💾 已保存 {len(manager)} 条记录到 {self.filename}")
def load(self, manager):
"""从 JSON 加载"""
if not os.path.exists(self.filename):
print(f"📂 数据文件不存在,将创建新文件")
return 0
try:
with open(self.filename, "r", encoding="utf-8") as f:
data = json.load(f)
count = 0
for sid, s_data in data.get("students", {}).items():
student = Student.from_dict(s_data)
manager.students[sid] = student
count += 1
print(f"📂 已加载 {count} 条记录")
return count
except (json.JSONDecodeError, KeyError) as e:
print(f"⚠️ 数据文件损坏: {e}")
return 0
def export_csv(self, manager, filename="students_export.csv"):
"""导出为 CSV"""
with open(filename, "w", encoding="utf-8-sig", newline="") as f:
writer = csv.writer(f)
# 表头
all_courses = set()
for s in manager.students.values():
for c in s.courses:
all_courses.add(c.name)
course_list = sorted(all_courses)
header = ["学号", "姓名", "年龄"] + course_list + ["总分", "平均分", "是否全部及格"]
writer.writerow(header)
# 数据
for s in manager.students.values():
row = [s.sid, s.name, s.age]
for cname in course_list:
c = s.get_course(cname)
row.append(c.score if c else "")
row.extend([s.total_score, f"{s.avg_score:.1f}", "是" if s.passed_all else "否"])
writer.writerow(row)
print(f"📤 已导出到 {filename}")
def import_csv(self, manager, filename):
"""从 CSV 导入"""
if not os.path.exists(filename):
print(f"❌ 文件不存在: {filename}")
return 0
from models import Course
count = 0
with open(filename, "r", encoding="utf-8-sig") as f:
reader = csv.DictReader(f)
for row in reader:
sid = row.get("学号", "").strip()
name = row.get("姓名", "").strip()
age = int(row.get("年龄", 0))
if not sid or not name:
continue
if sid not in manager.students:
manager.add_student(name, sid, age)
for key, value in row.items():
if key not in ("学号", "姓名", "年龄", "总分", "平均分", "是否全部及格"):
if value and value.strip().replace(".", "").isdigit():
manager.add_grade(sid, key, float(value))
count += 1
print(f"📥 已导入 {count} 条记录")
return count
5. MenuUI 菜单界面 (ui.py)
class MenuUI:
"""命令行菜单界面"""
def __init__(self, manager, storage):
self.manager = manager
self.storage = storage
def run(self):
"""主循环"""
while True:
self.show_menu()
choice = input("\n请选择 (0-9): ").strip()
actions = {
"1": self.add_student,
"2": self.add_grade,
"3": self.view_student,
"4": self.update_student,
"5": self.delete_student,
"6": self.search_student,
"7": self.show_ranking,
"8": self.show_stats,
"9": self.import_export,
"0": self.quit,
}
action = actions.get(choice)
if action:
action()
else:
print("❌ 无效选择,请输入 0-9")
def show_menu(self):
print("\n" + "=" * 50)
print("📚 学生管理系统 v1.0")
print("=" * 50)
print(f" 当前学生: {len(self.manager)} 人")
print("-" * 50)
print(" 1. ➕ 添加学生 2. 📝 添加/修改成绩")
print(" 3. 👤 查看学生 4. ✏️ 修改信息")
print(" 5. 🗑️ 删除学生 6. 🔍 搜索")
print(" 7. 🏆 成绩排名 8. 📊 统计分析")
print(" 9. 📤 导入导出 0. 🚪 退出")
print("=" * 50)
def add_student(self):
print("\n➕ 添加学生")
name = input(" 姓名: ").strip()
sid = input(" 学号: ").strip()
try:
age = int(input(" 年龄: ").strip())
except ValueError:
print("❌ 年龄必须是数字")
return
try:
self.manager.add_student(name, sid, age)
self.storage.save(self.manager)
print(f"✅ 已添加: {name} ({sid})")
except ValueError as e:
print(f"❌ {e}")
def add_grade(self):
sid = input("\n📝 输入学号: ").strip()
try:
student = self.manager.get_student(sid)
except KeyError as e:
print(f"❌ {e}")
return
print(f" 当前: {student}")
name = input(" 课程名: ").strip()
try:
score = float(input(" 成绩: ").strip())
except ValueError:
print("❌ 成绩必须是数字")
return
credit = input(" 学分 (默认1): ").strip()
credit = float(credit) if credit else 1.0
self.manager.add_grade(sid, name, score, credit)
self.storage.save(self.manager)
print(f"✅ 已记录: {student.name} - {name} = {score}")
def view_student(self):
sid = input("\n👤 输入学号: ").strip()
try:
s = self.manager.get_student(sid)
print(f"\n{'='*40}")
print(f" 姓名: {s.name}")
print(f" 学号: {s.sid}")
print(f" 年龄: {s.age}")
print(f" 创建: {s.created_at}")
print(f"{'='*40}")
if s.courses:
print(f" {'课程':10s} {'成绩':>6s} {'等级':>4s} {'状态':>4s}")
print(f" {'-'*28}")
for c in sorted(s.courses, key=lambda x: -x.score):
print(f" {c.name:10s} {c.score:6.1f} {c.grade:>4s} {'✅' if c.passed else '❌':>4s}")
print(f" {'-'*28}")
print(f" {'总分':10s} {s.total_score:6.1f}")
print(f" {'平均':10s} {s.avg_score:6.1f}")
else:
print(" 暂无成绩记录")
except KeyError as e:
print(f"❌ {e}")
def update_student(self):
sid = input("\n✏️ 输入学号: ").strip()
try:
s = self.manager.get_student(sid)
print(f" 当前: {s}")
name = input(f" 新姓名 (回车保持 '{s.name}'): ").strip()
age = input(f" 新年龄 (回车保持 '{s.age}'): ").strip()
kwargs = {}
if name: kwargs["name"] = name
if age:
try: kwargs["age"] = int(age)
except ValueError: print("⚠️ 年龄必须是数字,跳过")
if kwargs:
self.manager.update_student(sid, **kwargs)
self.storage.save(self.manager)
print(f"✅ 已更新")
else:
print(" 未修改")
except KeyError as e:
print(f"❌ {e}")
def delete_student(self):
sid = input("\n🗑️ 输入学号: ").strip()
try:
s = self.manager.get_student(sid)
confirm = input(f" 确认删除 '{s.name}'?(y/n): ").strip().lower()
if confirm == "y":
self.manager.remove_student(sid)
self.storage.save(self.manager)
print(f"✅ 已删除: {s.name}")
except KeyError as e:
print(f"❌ {e}")
def search_student(self):
keyword = input("\n🔍 搜索关键词: ").strip()
results = self.manager.search(keyword)
if results:
print(f"\n 找到 {len(results)} 条结果:")
for s in results:
print(f" {s}")
else:
print(" ❌ 未找到")
def show_ranking(self):
students = self.manager.ranking()
if not students:
print(" 📭 暂无数据")
return
print(f"\n🏆 成绩排名(共 {len(students)} 人)")
print(f" {'排名':4s} {'学号':8s} {'姓名':8s} {'总分':>6s} {'均分':>6s} {'课程数':>4s}")
print(" " + "-" * 42)
for i, s in enumerate(students, 1):
medal = "🥇🥈🥉"[i-1] if i <= 3 else f"{i:2d}"
print(f" {medal:4s} {s.sid:8s} {s.name:8s} {s.total_score:6.1f} {s.avg_score:6.1f} {s.course_count:4d}")
def show_stats(self):
stats = self.manager.overall_stats()
if not stats:
print(" 📭 暂无数据")
return
print(f"\n📊 整体统计")
print(f" 学生总数: {stats['total_students']}")
print(f" 课程总数: {stats['total_courses']}")
print(f" 平均分: {stats['avg_score']:.1f}")
print(f" 全部及格: {stats['all_passed']}人")
print(f"\n 课程列表: {', '.join(stats['course_list'])}")
for cname in stats["course_list"]:
cs = self.manager.course_stats(cname)
if cs:
print(f"\n 📚 {cname}:")
print(f" 人数: {cs['count']} 平均: {cs['avg']:.1f} 最高: {cs['max']:.1f} 最低: {cs['min']:.1f} 及格率: {cs['pass_rate']:.0f}%")
def import_export(self):
print("\n📤 导入导出")
print(" 1. 导出 CSV 2. 导入 CSV 3. 返回")
choice = input(" 请选择: ").strip()
if choice == "1":
self.storage.export_csv(self.manager)
elif choice == "2":
filename = input(" CSV 文件名: ").strip()
self.storage.import_csv(self.manager, filename)
self.storage.save(self.manager)
def quit(self):
self.storage.save(self.manager)
print("\n👋 再见!数据已保存。")
exit()
6. 完整主程序 (main.py)
"""
学生管理系统 v1.0
第一阶段毕业项目(Day 1-25)
"""
from manager import GradeManager
from storage import FileManager
from ui import MenuUI
def main():
print("=" * 50)
print("📚 学生管理系统 v1.0")
print("=" * 50)
# 初始化组件
storage = FileManager("students.json")
manager = GradeManager()
ui = MenuUI(manager, storage)
# 加载数据
storage.load(manager)
# 启动菜单
ui.run()
if __name__ == "__main__":
main()
7. 运行效果演示
$ python main.py
==================================================
📚 学生管理系统 v1.0
==================================================
📂 已加载 0 条记录
==================================================
📚 学生管理系统 v1.0
==================================================
当前学生: 0 人
--------------------------------------------------
1. ➕ 添加学生 2. 📝 添加/修改成绩
3. 👤 查看学生 4. ✏️ 修改信息
5. 🗑️ 删除学生 6. 🔍 搜索
7. 🏆 成绩排名 8. 📊 统计分析
9. 📤 导入导出 0. 🚪 退出
==================================================
请选择 (0-9): 1
➕ 添加学生
姓名: 张三
学号: 2024001
年龄: 20
✅ 已添加: 张三 (2024001)
请选择 (0-9): 2
📝 输入学号: 2024001
当前: [2024001] 张三, 20岁, 0科, 均分0.0, ✅ 全部及格
课程名: Python
成绩: 92
✅ 已记录: 张三 - Python = 92.0
请选择 (0-9): 7
🏆 成绩排名(共 1 人)
排名 学号 姓名 总分 均分 课程数
------------------------------------------
🥇 2024001 张三 92.0 92.0 1
8. 第一阶段总结 🎓
恭喜!你完成了 Python 基础语法的全部 25 天学习!
| | | |
|---|
| | | |
| | |
| | |
| | |
| | |
| | | |
| | |
| | |
| | |
| | def/args/kwargs/lambda/闭包 |
| | |
| | |
| | | |
| | @classmethod/@property/私有 |
| | |
| | __str__/__eq__/__add__/__call__ |
| | |
🎓 第一阶段毕业感言:
25 天前,你写了第一行 print("Hello World")。
今天,你已经能用 OOP 架构写出完整的管理系统——有数据模型、业务逻辑、文件持久化、用户界面。
这不是终点,而是起点。
从 Day 26 开始,我们将进入Python 进阶的世界——正则表达式、JSON/CSV 处理、datetime、迭代器、装饰器、多线程、网络编程...
你已经打下了坚实的基础,接下来的旅程会更精彩!🚀
🔮 预告: Day 26 正则表达式基础 — re 模块、元字符、match/search/findall。文本处理的瑞士军刀!