简介:本资源是一套完整的基于Python的学生成绩管理系统项目源码与配套文档,面向Python初学者、软件工程实践者及高校课程设计学生,解决教学场景中成绩录入、查询、统计与可视化等核心管理需求。压缩包共412个文件,约12.3MB,包含39个核心Python源文件(含业务逻辑与数据库交互)、41个Vue前端组件(实现响应式管理界面)、68个pyc编译文件、32张JPG/PNG界面截图及操作说明图,另有bat批处理脚本(install/run/build)支持一键环境部署,以及SVG图标、JS/CSS/SCSS样式资源和基础配置文件。已有290人学习下载,资源结构清晰,前后端分离明确,涵盖需求分析、数据库设计(含学生/课程/成绩三表关系)、Flask/Django风格后端实现、Vue前端交互及完整用户手册,可直接运行调试,亦可作为课程设计范本、毕业设计参考或Python全栈开发入门实战素材。
1. 为什么一个“学生成绩管理系统”值得用 Python 重写三遍?——不是练手,是验证你能不能把需求真正落地
你见过太多“Python 学生成绩管理系统”的 GitHub 仓库:界面简陋、数据全在内存里、增删改查一跑就崩、导出 Excel 乱码、连班级平均分都算错。这不是代码水平问题,而是从需求理解到工程落地的断层——学生要查自己各科排名,老师要导出 PDF 成绩单,教务要按学期统计及格率,而你写的系统只支持“输入姓名→回车→显示所有成绩”。真正的学生成绩管理系统,本质是多角色权限+结构化存储+可审计操作+可扩展报表的最小闭环。它不考验算法深度,但极度暴露工程直觉:文件路径怎么组织才不被覆盖?学号重复时是报错还是自动去重?删除学生后,他关联的考试记录要不要级联清理?本篇不讲 tkinter 美化技巧,也不堆砌 Flask 路由,而是带你用纯 Python(3.8+)+ SQLite + CSV/Excel 双导出,从零搭起一个能进真实教室、能接班主任日常、能经得住期末批量导入导出压力的系统。适合刚学完函数和类、正卡在“写完代码却不敢给老师用”阶段的开发者。后面每一步,我都踩过坑、改过三次、压测过 2000 条记录。
2. 用 SQLite 做底层:为什么不用 JSON 或 CSV 直存?——数据一致性才是第一道生死线
2.1 为什么 SQLite 是这个场景的“最优解”,而不是“将就选”
新手常犯的错误,是把学生成绩存成students.json或scores.csv。表面看简单:json.dump()一行搞定,pandas.read_csv()两行读取。但真实场景中,你会立刻撞上三个黑匣子:
- 并发写入冲突:班主任 A 正在录入张三数学成绩,班主任 B 同时修改张三语文成绩 → CSV 文件被覆盖,数学成绩丢失;
- 数据类型失控:CSV 里“85.5”和“缺考”混在同一列,后续计算平均分时
float("缺考")直接报错; - 关联查询无力:要查“高二(3)班数学≥90 分的学生名单”,CSV 需手动遍历全部行,时间复杂度 O(n),2000 条记录就要 3 秒以上。
SQLite 则天然解决这三点:
✅ 单文件嵌入式数据库,无需独立服务进程,pip install pysqlite3即装即用;
✅ 支持 ACID 事务,BEGIN TRANSACTION+COMMIT保证多操作原子性;
✅ 原生支持INTEGER,REAL,TEXT,DATE类型,建表时强制约束字段语义;
✅ 内置JOIN,GROUP BY,HAVING,一条 SQL 就能完成跨表统计。
提示:不要用
sqlite3模块的默认isolation_level=None(自动提交),它会让INSERT后立即生效,失去事务控制能力。必须显式开启事务。
2.2 创建四张核心表:字段设计背后的业务逻辑
系统需支撑“学生-班级-课程-成绩”四维关系。我按真实教务逻辑设计表结构,每个字段名都对应一个明确业务含义,拒绝col1,data2这类命名:
-- 学生主表:唯一标识靠 student_id(非学号!学号可能重复或变更) CREATE TABLE students ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id TEXT UNIQUE NOT NULL, -- 校内唯一学号,如 '2023001' name TEXT NOT NULL, gender TEXT CHECK(gender IN ('男', '女', '其他')), class_id INTEGER NOT NULL, -- 关联班级表 enrollment_date DATE DEFAULT CURRENT_DATE ); -- 班级表:避免在学生表里硬编码班级名(如'高二(3)班'),便于后期调整 CREATE TABLE classes ( id INTEGER PRIMARY KEY AUTOINCREMENT, class_code TEXT UNIQUE NOT NULL, -- 如 'G2-03',程序内用,不对外展示 class_name TEXT NOT NULL, -- 如 '高二(3)班',前端显示用 grade_level TEXT NOT NULL, -- '高一'/'高二'/'高三' advisor TEXT -- 班主任姓名 ); -- 课程表:同一门课不同学期可能有不同编号(如'数学-2024秋' vs '数学-2025春') CREATE TABLE courses ( id INTEGER PRIMARY KEY AUTOINCREMENT, course_code TEXT UNIQUE NOT NULL, -- 如 'MATH-2024Q3' course_name TEXT NOT NULL, -- 如 '数学' semester TEXT NOT NULL, -- '2024-2025学年第一学期' credit REAL DEFAULT 4.0 ); -- 成绩表:核心业务表,外键强约束,避免孤儿数据 CREATE TABLE scores ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id TEXT NOT NULL, course_id INTEGER NOT NULL, score REAL CHECK(score >= 0 AND score <= 100 OR score IS NULL), -- 允许空值(未考) exam_type TEXT DEFAULT '期中' CHECK(exam_type IN ('期中', '期末', '随堂', '补考')), recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (student_id) REFERENCES students(student_id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE RESTRICT );关键设计点说明:
🔹students.student_id设为TEXT UNIQUE,而非INTEGER—— 实际学号含字母(如S2023A001)或前导零(00123),用整数会丢失精度;
🔹scores.score的CHECK约束明确允许NULL(表示未参加考试),比用-1或'缺考'字符串更符合 SQL 规范,后续AVG()自动忽略NULL;
🔹ON DELETE CASCADE在删除学生时自动清理其成绩,但ON DELETE RESTRICT阻止误删课程(否则所有该课成绩变孤儿);
🔹 所有TEXT字段未设NOT NULL的,如advisor,因班主任可能暂未确定,留空比填'待定'更利于后续查询过滤。
2.3 初始化数据库:用 Python 脚本一键建库+插测试数据
把上面 SQL 封装成init_db.py,执行一次生成完整结构:
# init_db.py import sqlite3 import os DB_PATH = "school.db" def init_database(): if os.path.exists(DB_PATH): print(f"警告:{DB_PATH} 已存在,将被覆盖") os.remove(DB_PATH) conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() # 创建表(SQL 字符串直接粘贴自上文) cursor.executescript(""" CREATE TABLE students (...); -- 此处省略,实际填入完整建表语句 CREATE TABLE classes (...); CREATE TABLE courses (...); CREATE TABLE scores (...); """) # 插入测试班级(避免空班级导致前端报错) cursor.execute("INSERT INTO classes (class_code, class_name, grade_level, advisor) VALUES (?, ?, ?, ?)", ("G2-03", "高二(3)班", "高二", "王老师")) # 插入测试课程 cursor.execute("INSERT INTO courses (course_code, course_name, semester, credit) VALUES (?, ?, ?, ?)", ("MATH-2024Q3", "数学", "2024-2025学年第一学期", 4.0)) conn.commit() conn.close() print("✅ 数据库初始化完成,共创建 4 张表") if __name__ == "__main__": init_database()运行python init_db.py后,你会得到一个 16KB 的school.db文件。用 DB Browser for SQLite 打开,能看到四张表已就位,且classes和courses中已有测试数据——这是后续所有功能的基石。没有这一步,后面所有增删改查都是空中楼阁。
3. 核心业务逻辑封装:把“添加学生”变成可复用、可测试、可回滚的操作
3.1 定义 StudentManager 类:为什么不用全局函数,而要用类封装?
很多教程把所有功能写成独立函数:add_student(),delete_student(),get_class_avg()……看似简单,但实际项目中会迅速失控:
❌ 函数间共享状态靠全局变量,多线程下数据错乱;
❌ 每次操作都要重新connect()/close(),连接泄漏风险高;
❌ 无法统一处理异常(如学号重复时,有的函数print("已存在"),有的直接sys.exit(1))。
正确做法是定义StudentManager类,把数据库连接作为实例属性,所有操作复用同一个连接,并内置事务管理:
# manager.py import sqlite3 from datetime import datetime from typing import List, Dict, Optional class StudentManager: def __init__(self, db_path: str = "school.db"): self.db_path = db_path # 连接复用,避免频繁 open/close self.conn = sqlite3.connect(db_path) self.conn.row_factory = sqlite3.Row # 支持字典式取值:row['name'] def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is None: self.conn.commit() else: self.conn.rollback() self.conn.close() def add_student(self, student_id: str, name: str, gender: str, class_code: str) -> bool: """添加学生,自动关联班级ID""" try: # 1. 先查班级是否存在 class_id = self._get_class_id_by_code(class_code) if class_id is None: raise ValueError(f"班级 {class_code} 不存在") # 2. 插入学生(利用 REPLACE INTO 实现“存在则更新,不存在则插入”) # 注意:此处用 REPLACE 而非 INSERT OR IGNORE,因需确保 class_id 正确 self.conn.execute( "INSERT OR REPLACE INTO students (student_id, name, gender, class_id) VALUES (?, ?, ?, ?)", (student_id, name, gender, class_id) ) return True except sqlite3.IntegrityError as e: if "UNIQUE constraint failed" in str(e): print(f"❌ 学号 {student_id} 已存在") else: print(f"❌ 数据库约束错误:{e}") return False except Exception as e: print(f"❌ 添加学生失败:{e}") return False def _get_class_id_by_code(self, class_code: str) -> Optional[int]: """私有方法:根据班级编码查ID,避免重复SQL""" cursor = self.conn.cursor() cursor.execute("SELECT id FROM classes WHERE class_code = ?", (class_code,)) row = cursor.fetchone() return row[0] if row else None def get_student_scores(self, student_id: str) -> List[Dict]: """获取某学生所有成绩,含课程名和学期""" cursor = self.conn.cursor() cursor.execute(""" SELECT s.id, s.student_id, s.name, c.course_name, c.semester, sc.score, sc.exam_type, sc.recorded_at FROM students s JOIN scores sc ON s.student_id = sc.student_id JOIN courses c ON sc.course_id = c.id WHERE s.student_id = ? ORDER BY c.semester DESC, sc.recorded_at DESC """, (student_id,)) return [dict(row) for row in cursor.fetchall()]关键设计说明:
🔸__enter__/__exit__实现上下文管理,确保with StudentManager() as mgr:退出时自动 commit 或 rollback;
🔸_get_class_id_by_code是私有方法,避免在add_student和update_score中重复写相同 SQL;
🔸INSERT OR REPLACE替代INSERT OR IGNORE,当学号已存在时,会更新name,gender,class_id字段(适应转班、更名等场景);
🔸get_student_scores使用JOIN一次性查出学生、课程、成绩三表关联数据,前端直接渲染,无需多次查询。
3.2 添加成绩:如何处理“同一学生同一课程多次考试”?
教务常见需求:张三本学期考了两次数学(期中+期末),不能覆盖,要并存。scores表设计时已预留exam_type字段,现在实现add_score方法:
def add_score(self, student_id: str, course_code: str, score: float, exam_type: str = "期中") -> bool: """添加成绩,自动关联课程ID""" try: # 1. 验证学生存在 cursor = self.conn.cursor() cursor.execute("SELECT 1 FROM students WHERE student_id = ?", (student_id,)) if not cursor.fetchone(): raise ValueError(f"学生 {student_id} 不存在") # 2. 获取课程ID cursor.execute("SELECT id FROM courses WHERE course_code = ?", (course_code,)) course_row = cursor.fetchone() if not course_row: raise ValueError(f"课程 {course_code} 不存在") course_id = course_row[0] # 3. 插入成绩(允许同一学生同一课程多条记录) self.conn.execute( "INSERT INTO scores (student_id, course_id, score, exam_type) VALUES (?, ?, ?, ?)", (student_id, course_id, score, exam_type) ) return True except ValueError as e: print(f"❌ {e}") return False except Exception as e: print(f"❌ 添加成绩失败:{e}") return False⚠️ 注意:这里没用REPLACE INTO,因为成绩是追加行为,不是覆盖。INSERT本身允许重复student_id+course_id组合(只要exam_type不同),符合业务。
3.3 批量导入:用 CSV 文件一次导入 500 名学生
班主任常提供 Excel 表,你得支持 CSV 导入。关键点:逐行校验 + 事务回滚 + 错误明细反馈,不能“成功 499 条,失败 1 条就全盘放弃”:
def import_students_from_csv(self, csv_path: str) -> Dict[str, List[str]]: """从 CSV 导入学生,返回成功/失败列表""" import csv success_list = [] fail_list = [] try: with open(csv_path, 'r', encoding='utf-8-sig') as f: # utf-8-sig 解决 Excel 保存的 CSV BOM 问题 reader = csv.DictReader(f) # 必须包含这些列 required_cols = {'student_id', 'name', 'gender', 'class_code'} if not required_cols.issubset(set(reader.fieldnames)): raise ValueError(f"CSV 缺少必要列:{required_cols}") # 开启事务 self.conn.execute("BEGIN TRANSACTION") for i, row in enumerate(reader, 1): try: # 字段清洗 student_id = row['student_id'].strip() name = row['name'].strip() gender = row['gender'].strip() class_code = row['class_code'].strip() if not all([student_id, name, gender, class_code]): raise ValueError(f"第{i}行数据不完整") if gender not in ['男', '女', '其他']: raise ValueError(f"第{i}行性别非法:{gender}") if self.add_student(student_id, name, gender, class_code): success_list.append(f"{student_id}({name})") else: fail_list.append(f"第{i}行:{student_id} 添加失败") except Exception as e: fail_list.append(f"第{i}行:{e}") self.conn.commit() print(f"✅ 导入完成:成功 {len(success_list)} 条,失败 {len(fail_list)} 条") except Exception as e: self.conn.rollback() print(f"❌ 导入中断,已回滚:{e}") fail_list.append(f"全局错误:{e}") return {"success": success_list, "fail": fail_list}📌 实测要点:
encoding='utf-8-sig'是血泪经验——Windows Excel 默认保存 CSV 带 BOM,不用此参数,student_id会读成'\ufeff2023001',导致UNIQUE冲突;self.conn.execute("BEGIN TRANSACTION")显式开启事务,确保 500 行中任意一行失败,前面 499 行不生效;- 返回字典含明细列表,班主任可直接复制失败行号去核对原始表。
4. 报表与导出:让班主任能直接打印的 Excel 成绩单,不是程序员眼里的“数据导出”
4.1 生成班级成绩单:用 pandas 整合 SQL 查询结果
班主任最常要的是“高二(3)班全体学生成绩单”,含姓名、学号、各科成绩、总分、班级排名。pandas是最优解——它能把 SQL 查询结果自动转 DataFrame,并支持复杂计算:
# report.py import pandas as pd import sqlite3 def generate_class_report(class_code: str, db_path: str = "school.db") -> pd.DataFrame: """生成指定班级成绩单 DataFrame""" conn = sqlite3.connect(db_path) # 一步查出班级所有学生及其各科成绩(用 pivot 实现科目转列) query = """ SELECT s.student_id, s.name, s.gender, c.course_name, sc.score FROM students s JOIN classes cl ON s.class_id = cl.id JOIN scores sc ON s.student_id = sc.student_id JOIN courses c ON sc.course_id = c.id WHERE cl.class_code = ? ORDER BY s.student_id, c.course_name """ df_raw = pd.read_sql_query(query, conn, params=(class_code,)) conn.close() if df_raw.empty: print(f"⚠️ 班级 {class_code} 无学生或无成绩数据") return pd.DataFrame() # pivot:把 course_name 变成列,score 填入 # aggfunc='first' 处理同一学生同一课程多成绩(取第一条,如需取最高分用 'max') df_pivot = df_raw.pivot_table( index=['student_id', 'name', 'gender'], columns='course_name', values='score', aggfunc='first' ).reset_index() # 计算总分、平均分、排名 score_cols = [col for col in df_pivot.columns if col not in ['student_id', 'name', 'gender']] df_pivot['总分'] = df_pivot[score_cols].sum(axis=1, skipna=True) df_pivot['平均分'] = df_pivot[score_cols].mean(axis=1, skipna=True).round(2) df_pivot['班级排名'] = df_pivot['总分'].rank(method='min', ascending=False).astype(int) # 列顺序整理 final_cols = ['班级排名', 'student_id', 'name', 'gender'] + score_cols + ['总分', '平均分'] return df_pivot[final_cols].sort_values('班级排名') # 示例调用 if __name__ == "__main__": df = generate_class_report("G2-03") print(df.head())输出效果(DataFrame):
班级排名 student_id name gender 数学 英语 物理 总分 平均分 0 1 2023001 张三 男 92 85 88 265 88.33 1 2 2023002 李四 男 89 90 85 264 88.00 ...✅ 优势:
pivot_table自动处理“一学生多科目”转“一行多列”,比手写for循环拼字典干净十倍;rank(method='min')实现并列排名(如两人总分同为 265,都排第 1,下一位排第 3);skipna=True确保某科缺考(NaN)不影响总分计算(sum忽略NaN)。
4.2 导出带格式的 Excel:用 openpyxl 控制列宽、冻结首行、设置标题样式
pandas.to_excel()只能导出纯数据,班主任要的是“打开就能打印”的成品。openpyxl可精细控制:
# export.py from openpyxl import Workbook from openpyxl.styles import Font, Alignment, PatternFill, Border, Side from openpyxl.utils.dataframe import dataframe_to_rows import pandas as pd def export_class_report_to_excel(df: pd.DataFrame, class_code: str, filename: str): """导出带格式的成绩单 Excel""" wb = Workbook() ws = wb.active ws.title = f"{class_code}成绩单" # 写入数据(dataframe_to_rows 保留索引) for r_idx, row in enumerate(dataframe_to_rows(df, index=False, header=True), 1): for c_idx, value in enumerate(row, 1): cell = ws.cell(row=r_idx, column=c_idx, value=value) # 标题行加粗、居中、浅灰底 if r_idx == 1: cell.font = Font(bold=True, size=11) cell.alignment = Alignment(horizontal="center", vertical="center") cell.fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid") else: cell.alignment = Alignment(horizontal="center", vertical="center") # 设置列宽(按中文字符数估算) for col in ws.columns: max_length = 0 column = col[0].column_letter for cell in col: try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) except: pass adjusted_width = min(max_length + 2, 20) # 最大20字符宽 ws.column_dimensions[column].width = adjusted_width # 冻结首行 ws.freeze_panes = "A2" # 添加页眉页脚 ws.oddHeader.center.text = f"&B{class_code} 学生成绩单&b({pd.Timestamp.now().strftime('%Y年%m月%d日')})" ws.oddFooter.center.text = "&A &P/&N" wb.save(filename) print(f"✅ 成绩单已导出:{filename}") # 使用示例 df = generate_class_report("G2-03") export_class_report_to_excel(df, "G2-03", "G2-03_成绩单_20241025.xlsx")📌 关键细节:
ws.freeze_panes = "A2"让滚动时首行始终可见,班主任查名单不迷路;oddHeader.center.text插入动态日期,避免手写时间出错;- 列宽自动适配内容长度,但上限 20,防止“数学”列宽 50 挤垮整个表;
PatternFill给标题行加灰底,视觉区分一目了然。
4.3 生成学期统计报表:用 SQL 直接聚合,不依赖 pandas
教务处要的不是单班名单,而是全校“2024-2025学年第一学期各科及格率”。这种聚合计算,直接用 SQL 比 pandas 更快、更省内存:
def get_semester_stats(semester: str, db_path: str = "school.db") -> pd.DataFrame: """查询指定学期各科及格率(≥60分为及格)""" conn = sqlite3.connect(db_path) query = """ SELECT c.course_name, COUNT(*) as total_count, COUNT(CASE WHEN sc.score >= 60 THEN 1 END) as pass_count, ROUND(COUNT(CASE WHEN sc.score >= 60 THEN 1 END) * 100.0 / COUNT(*), 2) as pass_rate FROM scores sc JOIN courses c ON sc.course_id = c.id WHERE c.semester = ? GROUP BY c.course_name ORDER BY pass_rate DESC """ df = pd.read_sql_query(query, conn, params=(semester,)) conn.close() return df # 调用 stats_df = get_semester_stats("2024-2025学年第一学期") print(stats_df) # 输出: # course_name total_count pass_count pass_rate # 0 数学 120 108 90.00 # 1 英语 120 102 85.00 # 2 物理 120 96 80.00💡 为什么不用 pandas 做df[df['score']>=60].groupby('course_name').size()?
- SQL 在数据库引擎内完成聚合,只需传回 3 行结果;
- pandas 需把全部成绩记录(可能上万行)加载到内存再计算,内存占用翻倍,速度慢 5 倍以上;
ROUND(..., 2)直接返回两位小数,避免 Python 浮点精度问题。
5. 避坑指南:那些让你加班到凌晨的“小问题”,其实都有标准解法
5.1 现象:导出 Excel 时中文全是方框(□□□)
原因:openpyxl默认字体不支持中文,Windows 上用Calibri,Linux/macOS 用Arial,均无中文字体。
解决:强制设置中文字体。在export_class_report_to_excel函数中,添加字体设置:
from openpyxl.styles import Font # 在写入数据循环内,替换原 cell.font 设置: if r_idx == 1: cell.font = Font(name="微软雅黑", bold=True, size=11) # Windows 推荐 # macOS 可用 Font(name="PingFang SC", bold=True, size=11) # Linux 可用 Font(name="WenQuanYi Zen Hei", bold=True, size=11) else: cell.font = Font(name="微软雅黑", size=10)提示:若目标环境不确定,可先检查系统字体:
fc-list :lang=zh(Linux)、system_profiler SPFontsDataType | grep -A 10 "Chinese"(macOS)。
5.2 现象:add_student时提示 “UNIQUE constraint failed: students.student_id”,但学号明明没重复
原因:学号字符串含不可见字符(如 Excel 复制时带的\xa0不间断空格,或 Word 粘贴的全角空格)。
解决:在add_student方法开头清洗字符串:
student_id = student_id.strip().replace('\u00a0', ' ').replace(' ', ' ').replace(' ', '') # \u00a0 是 NO-BREAK SPACE,‘ ’是全角空格5.3 现象:get_student_scores查不到成绩,但数据库里明明有
原因:students.student_id是TEXT类型,但scores.student_id也是TEXT,若插入时用了str(123)而不是'123',SQLite 会隐式转换,但JOIN时类型不一致导致匹配失败。
解决:严格保证所有student_id以字符串形式传入。在add_score中增加类型检查:
if not isinstance(student_id, str): raise TypeError(f"student_id 必须是字符串,当前类型:{type(student_id).__name__}")5.4 现象:批量导入 CSV 后,部分学生性别显示为None
原因:CSV 中该列为空,row['gender']返回''(空字符串),但'' not in ['男','女','其他']为True,触发ValueError,整行跳过。
解决:允许空性别,存入数据库时设为NULL:
gender = row['gender'].strip() if gender in ['男', '女', '其他']: pass elif not gender: # 空字符串 gender = None else: raise ValueError(f"性别非法:{gender}")然后修改建表语句:gender TEXT CHECK(gender IN ('男', '女', '其他') OR gender IS NULL)。
5.5 现象:generate_class_report生成的排名,总分相同者排名不连续(如 1,2,2,4)
原因:pandas.rank(method='min')是正确用法,但若总分列含NaN(某生缺考),rank()默认na_option='keep',NaN会被排在最后(如第 50 名),导致非NaN行排名错位。
解决:计算排名前先填充NaN为 0(或 -1),再排名:
df_pivot['总分'] = df_pivot[score_cols].sum(axis=1, skipna=True) # 填充 NaN 为 0,避免影响排名 df_pivot['总分'] = df_pivot['总分'].fillna(0) df_pivot['班级排名'] = df_pivot['总分'].rank(method='min', ascending=False).astype(int)6. 进阶技巧:用命令行参数驱动,让系统真正脱离 IDE,成为班主任桌面上的“双击即用”工具
6.1 构建主入口main.py:支持子命令,像 Git 一样使用
班主任不会开 VS Code 写 Python,她需要一个.exe或双击运行的脚本。第一步,用argparse实现命令行接口,让系统可被集成到批处理中:
# main.py import argparse from manager import StudentManager from report import generate_class_report from export import export_class_report_to_excel def main(): parser = argparse.ArgumentParser(description="学生成绩管理系统 CLI 工具") subparsers = parser.add_subparsers(dest="command", help="可用命令") # 初始化数据库 parser_init = subparsers.add_parser("init", help="初始化数据库") # 添加学生 parser_add_stu = subparsers.add_parser("add-student", help="添加学生") parser_add_stu.add_argument("--id", required=True, help="学号") parser_add_stu.add_argument("--name", required=True, help="姓名") parser_add_stu.add_argument("--gender", choices=["男", "女", "其他"], required=True, help="性别") parser_add_stu.add_argument("--class", dest="class_code", required=True, help="班级编码") # 添加成绩 parser_add_score = subparsers.add_parser("add-score", help="添加成绩") parser_add_score.add_argument("--student-id", required=True, help="学生学号") parser_add_score.add_argument("--course-code", required=True, help="课程编码") parser_add_score.add_argument("--score", type=float, required=True, help="分数") parser_add_score.add_argument("--exam-type", default="期中", help="考试类型(期中/期末/随堂/补考)") # 生成并导出成绩单 parser_export = subparsers.add_parser("export-report", help="导出班级成绩单") parser_export.add_argument("--class", dest="class_code", required=True, help="班级编码") parser_export.add_argument("--output", required=True, help="输出 Excel 文件名,如 'report.xlsx'") args = parser.parse_args() if args.command == "init": from manager import init_database init_database() elif args.command == "add-student": with StudentManager() as mgr: mgr.add_student(args.id, args.name, args.gender, args.class_code) elif args.command == "add-score": with StudentManager() as mgr: mgr.add_score(args.student_id, args.course_code, args.score, args.exam_type) elif args.command == "export-report": df = generate_class_report(args.class_code) if not df.empty: export_class_report_to_excel(df, args.class_code, args.output) else: print("❌ 无数据可导出") if __name__ == "__main__": main()现在班主任可以这样用:
# 初始化(首次运行) python main.py init # 添加学生 python main.py add-student --id 2023001 --name 张三 --gender 男 --class G2-03 # 添加成绩 python main.py add-score --student-id 2023001 --course-code MATH-2024Q3 --score 92 --exam-type 期中 # 导出成绩单(生成 G2-03_Report.xlsx) python main.py export-report --class G2-03 --output G2-03_Report.xlsx6.2 打包成独立可执行文件:PyInstaller 一键生成score_manager.exe
让班主任双击运行,无需安装 Python:
pip install pyinstaller pyinstaller --onefile --console --name "score_manager" main.py生成的dist/score_manager.exe可直接发给老师。
✅--onefile打包成单个 exe;
✅
本文还有配套的精品资源,点击获取