1. 项目概述与需求分析
本教程将带你从零开始,用 Python 标准库自带的Tkinter构建一个带图形界面的教育测验软件,并最终打包成单个 EXE 文件。整个项目不依赖任何第三方付费库,代码量精简、逻辑清晰,非常适合编程新手练手,也可以作为教师制作课堂测验工具的参考模板。
1.1 核心功能清单
- 图形化界面:使用 Tkinter 构建窗口、按钮、单选框、标签等标准组件
- 题库加载:从 JSON 文件读取题目、选项和正确答案,零基础也能修改题库内容
- 逐题作答:用户选择答案后点击“下一题”自动判断对错并计分
- 实时分数反馈:每完成一题即时显示当前得分和正确率
- 测验结果报告:全部答完后弹出成绩总结窗口,展示得分、正确率和用时
- 打包为 EXE:使用 PyInstaller 将 Python 脚本打包成独立 EXE,无需安装 Python 便可分发
1.2 技术栈选型
| 层级 | 技术 | 说明 |
|---|---|---|
| 编程语言 | Python 3.8+ | 无需额外安装,Windows/macOS 均可使用 |
| GUI 框架 | Tkinter | Python 自带标准库,零额外依赖 |
| 数据格式 | JSON | 轻量级文本格式,方便修改题库 |
| 打包工具 | PyInstaller | 将 Python 脚本打包为单个 EXE 文件 |
2. 环境准备与项目搭建
2.1 基础环境要求
- Python 3.8 及以上版本(Tkinter 已内置,无需单独安装)
- 任意文本编辑器(推荐 VS Code 或 PyCharm)
- Windows 10/11 或 macOS 操作系统
2.2 确认 Tkinter 可用
打开终端或命令行,输入以下命令验证 Tkinter 是否可用。如果弹出一个空白小窗口,说明环境就绪:
# Windows PowerShell / CMD python -m tkinter 或者进入 Python 交互环境测试 python -c "import tkinter; tkinter.Tk().mainloop()"如果提示ModuleNotFoundError: No module named '_tkinter',请重新安装 Python 并确保勾选“tcl/tk and IDLE”选项。
2.3 项目文件结构
新建项目文件夹quiz_app,按以下结构组织文件:
quiz_app/ ├── questions.json # 题库文件(JSON 格式) ├── quiz_app.py # 主程序入口 └── build/ # 打包输出目录(自动生成)3. 题库设计:JSON 数据格式
题库以 JSON 格式存储,结构清晰易懂。每个题目包含题干、四个选项和正确答案索引。以下是一份示例题库,保存为questions.json:
[ { "question": "Python 中用于定义函数的关键字是?", "options": ["func", "define", "def", "function"], "answer": 2 }, { "question": "Tkinter 是 Python 的什么库?", "options": ["数据分析库", "GUI 标准库", "网络请求库", "图像处理库"], "answer": 1 }, { "question": "以下哪个方法可以获取列表长度?", "options": ["length()", "size()", "count()", "len()"], "answer": 3 }, { "question": "Python 中哪个符号表示单行注释?", "options": ["//", "#", "/*", "--"], "answer": 1 }, { "question": "JSON 的全称是?", "options": [ "Java Standard Object Notation", "JavaScript Object Notation", "Java Serialized Object Network", "JavaScript Online Network" ], "answer": 1 } ]字段说明:
question:题干文本options:包含 4 个选项的列表answer:正确答案的索引(从 0 开始),与 options 列表对应
4. 测验逻辑核心实现
在编写界面之前,先把测验的核心逻辑封装成一个独立类,便于后续和 GUI 层解耦。
import json import time class QuizEngine: """测验引擎:管理题目、计分、计时""" def __init__(self, questions_file): with open(questions_file, "r", encoding="utf-8") as f: self.questions = json.load(f) self.total = len(self.questions) self.current_index = 0 self.score = 0 self.start_time = time.time() def get_current_question(self): """返回当前题目数据,或 None(已完卷)""" if self.current_index >= self.total: return None return self.questions[self.current_index] def check_answer(self, user_choice): """判断用户选项是否正确,正确则加 1 分,同时移动到下一题""" q = self.get_current_question() if q is None: return None is_correct = (user_choice == q["answer"]) if is_correct: self.score += 1 self.current_index += 1 return is_correct def is_finished(self): """是否已完成全部题目""" return self.current_index >= self.total def get_progress(self): """返回 (当前题号, 总分, 正确数)""" return self.current_index, self.total, self.score def get_elapsed_time(self): """返回已用秒数""" return int(time.time() - self.start_time)</code></pre> 这个引擎类把所有题目控制逻辑封装起来,不依赖任何界面细节,方便后续单独测试和复用。 5. 图形界面构建:Tkinter 窗口 5.1 主窗口骨架 首先创建程序入口和主窗口框架。窗口标题设为“AI 编程教育测验”,固定窗口尺寸并居中显示: import tkinter as tk from tkinter import messagebox class QuizApp: """测验应用主窗口""" def init(self, root, engine): self.root = root self.engine = engine self.root.title("AI 编程教育测验") self.root.geometry("700x500") self.root.resizable(False, False) # 如果使用 Python 3.8 以下版本,可能需要手动居中 self.root.eval("tk::PlaceWindow . center") self.build_ui() self.display_question() def build_ui(self): """构建界面布局""" # 标题标签 self.title_label = tk.Label( self.root, text="编程知识测验", font=("Microsoft YaHei", 18, "bold"), fg="#333333" ) self.title_label.pack(pady=(20, 10)) # 分数 &amp; 进度标签 self.status_label = tk.Label( self.root, text="", font=("Microsoft YaHei", 11), fg="#666666" ) self.status_label.pack(pady=(0, 10)) 题干显示区域 self.question_label = tk.Label( self.root, text="", font=("Microsoft YaHei", 14), wraplength=600, justify="left", fg="#222222" ) self.question_label.pack(pady=(10, 20)) 选项区域(使用 Radiobutton) self.option_var = tk.IntVar(value=-1) # -1 表示未选中 self.option_buttons = [] option_frame = tk.Frame(self.root) option_frame.pack(pady=(0, 20)) for i in range(4): btn = tk.Radiobutton( option_frame, text="", variable=self.option_var, value=i, font=("Microsoft YaHei", 12), anchor="w", width=50, indicatoron=True, padx=10, pady=4 ) btn.pack(fill="x", pady=3) self.option_buttons.append(btn) 提交按钮 self.submit_btn = tk.Button( self.root, text="提交答案 →", font=("Microsoft YaHei", 13, "bold"), bg="#4A90D9", fg="white", activebackground="#357ABD", activeforeground="white", relief="flat", padx=30, pady=8, cursor="hand2", command=self.on_submit ) self.submit_btn.pack(pady=(10, 20))</code></pre> 5.2 题目展示与选项渲染 每次调用 display_question 方法都会从引擎获取当前题目数据,并更新界面上的题干文本和选项内容: def display_question(self): """从引擎获取当前题目并渲染到界面""" q = self.engine.get_current_question() if q is None: return 更新进度状态 idx, total, score = self.engine.get_progress() self.status_label.config(text=f"第 {idx + 1}/{total} 题 | 已得分:{score} 分") 更新题干 self.question_label.config(text=q["question"]) 更新选项 self.option_var.set(-1) for i, btn in enumerate(self.option_buttons): if i &lt; len(q["options"]): btn.config(text=q["options"][i], state="normal") else: btn.config(text="", state="disabled")</code></pre> 5.3 提交答案与计分反馈 点击“提交答案”按钮后,程序会判断用户选择是否正确,弹出即时反馈弹窗,然后自动进入下一题或结束测验: def on_submit(self): """处理提交答案事件""" user_choice = self.option_var.get() if user_choice == -1: messagebox.showwarning("请选择", "请先选择一个答案再提交!") return is_correct = self.engine.check_answer(user_choice) 即时反馈 if is_correct: feedback = "✓ 回答正确!加 1 分" else: q = self.engine.questions[self.engine.current_index - 1] correct_option = q["options"][q["answer"]] feedback = f"✗ 回答错误。正确答案是:{correct_option}" messagebox.showinfo("结果", feedback) 判断是否结束 if self.engine.is_finished(): self.show_result() else: self.display_question()</code></pre> 5.4 测验结果报告 全部题目作答完毕后,弹出成绩总结窗口,展示得分、正确率和用时: def show_result(self): """显示测验结果报告""" _, total, score = self.engine.get_progress() elapsed = self.engine.get_elapsed_time() percent = round(score / total * 100, 1) result_text = ( f"测验结束!\n\n" f"总题数:{total} 题\n" f"正确数:{score} 题\n" f"正确率:{percent}%\n" f"用时:{elapsed} 秒\n\n" ) if percent &gt;= 80: result_text += "🌟 很棒,继续保持!" elif percent &gt;= 60: result_text += "👍 不错,再复习一下知识点吧!" else: result_text += "📚 需要加强学习,建议回顾相关课程。" messagebox.showinfo("测验成绩单", result_text) 询问是否重新开始 if messagebox.askyesno("是否继续", "要重新开始测验吗?"): self.root.destroy() # 简易做法:关闭窗口后重新运行 else: self.root.destroy()</code></pre> 整合运行:入口代码 将上述所有代码整合到 quiz_app.py 的底部,添加程序入口: if name == "main": root = tk.Tk() engine = QuizEngine("questions.json") app = QuizApp(root, engine) root.mainloop() 运行方式: 确保 questions.json 和 quiz_app.py 在同一目录下 终端执行:python quiz_app.py 窗口弹出后即可开始答题 打包为 EXE 文件 7.1 安装 PyInstaller pip install pyinstaller 7.2 执行打包命令 在 quiz_app 目录下打开终端,执行以下命令: 基础打包(有命令行窗口) pyinstaller --onefile --add-data "questions.json;." quiz_app.py 推荐:静默打包(无命令行窗口 + 自定义图标) pyinstaller --onefile --windowed --add-data "questions.json;." --icon=app.ico --name "AI编程测验" quiz_app.py 参数说明: --onefile:打包成单个 EXE 文件 --windowed:不显示命令行窗口(纯 GUI 运行) --add-data:将 questions.json 打包进 EXE 内部 --icon:设置 EXE 图标(需要准备 .ico 格式图标文件) --name:输出的 EXE 文件名 7.3 打包后适配 JSON 路径 打包成 EXE 后,questions.json 会被内置到临时目录。需要在 QuizEngine.init 中添加路径处理逻辑: import os import sys def get_resource_path(relative_path): """获取资源文件的绝对路径,兼容开发环境和 EXE 打包环境""" if getattr(sys, 'frozen', False): PyInstaller 打包后的临时解压目录 base_dir = sys._MEIPASS else: base_dir = os.path.abspath(".") return os.path.join(base_dir, relative_path) class QuizEngine: def init(self, questions_file): file_path = get_resource_path(questions_file) with open(file_path, "r", encoding="utf-8") as f: self.questions = json.load(f) ... 其余初始化代码不变 7.4 打包产物 打包完成后,在 dist 目录下会生成单个 EXE 文件,大小约 10-20MB。将该 EXE 文件发送给任何人,无需安装 Python 即可直接运行。 8. 扩展建议 随机抽题:在 QuizEngine.init 中添加 random.shuffle(self.questions),每次测验顺序不同 错题回顾:收集错题,测验结束后展示错题列表和正确答案 多题库切换:通过下拉菜单或启动参数选择不同的 JSON 题库文件 计时压力模式:为每题添加倒计时,超时自动跳过 Excel 题库导入:支持从 .xlsx 文件批量导入题目,方便教师出题 9. 总结 本文从零开始,带你构建了一个完整、可直接运行的 Tkinter 教育测验软件。整个项目仅使用 Python 标准库,代码量约 150 行,结构清晰、易于修改和扩展。最后通过 PyInstaller 打包为单个 EXE,实现真正的“一次开发、零门槛分发”。无论是编程初学者还是教育工作者,都可以基于此模板快速制作适合自己的测验工具。