AI编程开发教育测验EXE:零基础用Python+Tkinter生成带界面的练习软件
1. AI编程开发教育测验EXE
在教育场景中,交互式测验软件是巩固知识、检验学习成果的高效工具。但对于没有编程基础的教育工作者来说,定制一款属于自己的练习软件往往门槛很高。本文将从零开始,带你用 Python + Tkinter 构建一个带图形界面的教育测验程序,并最终打包成可独立运行的 EXE 文件,方便在任意 Windows 电脑上分发使用。
学完本文后,你将掌握:
- 使用 Tkinter 快速搭建测验界面;
- 从题库中随机抽题并实现计时与评分;
- 用 PyInstaller 将程序打包为单文件 EXE。
2. 环境准备
在开始编码之前,需要先安装 Python 以及所需的第三方库。
2.1 安装 Python
前往 Python 官方网站 下载最新版 Python(推荐 3.9 及以上),安装时务必勾选「Add Python to PATH」选项。安装完成后,在命令行中验证:
python --version
2.2 安装 PyInstaller
Tkinter 是 Python 标准库的一部分,无需额外安装。打包工具 PyInstaller 需要手动安装:
pip install pyinstaller
至此,开发与打包环境准备就绪,新建一个项目文件夹(如 quiz_app),正式开始编码。
3. 设计题库结构
题库是测验程序的核心,我们用 JSON 文件来存储题目,既方便读取又易于扩充。在项目文件夹中新建 quiz_data.json:
{
"title": "Python 基础测验",
"time_limit": 600,
"questions": [
{
"id": 1,
"question": "Python 中用于定义函数的关键字是?",
"options": ["func", "define", "def", "function"],
"answer": 2
},
{
"id": 2,
"question": "以下哪个不是 Python 的合法变量名?",
"options": ["_name", "2var", "my_var", "data1"],
"answer": 1
},
{
"id": 3,
"question": "list(range(5)) 的输出是?",
"options": ["[1,2,3,4,5]", "[0,1,2,3,4]", "[0,1,2,3,4,5]", "[1,2,3,4]"],
"answer": 1
},
{
"id": 4,
"question": "Python 中字符串格式化方法 .format() 属于哪个版本引入?",
"options": ["Python 2.6", "Python 3.0", "Python 2.0", "Python 3.6"],
"answer": 1
},
{
"id": 5,
"question": "以下哪个数据类型是可变的?",
"options": ["tuple", "str", "int", "list"],
"answer": 3
}
]
}
每个题目包含题目文本、四个选项以及正确答案的索引(从0开始)。根节点还记录了测验标题和总时间限制(单位:秒),后续界面会用到这些字段。
4. 搭建 Tkinter 测验界面
新建主程序文件 main.py,逐步实现完整的测验功能。以下代码从零搭建界面,包含欢迎页、答题页和结果页三个视图,结构清晰、注释详尽。
4.1 框架与常量
import tkinter as tk
from tkinter import messagebox
import json
import random
import os
class QuizApp:
def __init__(self, root):
self.root = root
self.root.title("AI 编程开发教育测验")
self.root.geometry("700x520")
self.root.resizable(False, False)
# 配色
self.COLOR_BG = "#f5f7fa"
self.COLOR_CARD = "#ffffff"
self.COLOR_PRIMARY = "#4a90d9"
self.COLOR_TEXT = "#333333"
self.COLOR_CORRECT = "#27ae60"
self.COLOR_WRONG = "#e74c3c"
self.root.configure(bg=self.COLOR_BG)
# 加载题库
self.load_questions()
# 状态变量
self.current_q = 0
self.score = 0
self.user_answers = []
self.shuffled_indices = []
self.time_left = 0
self.timer_id = None
# 显示欢迎页
self.show_welcome()
def load_questions(self):
try:
with open("quiz_data.json", "r", encoding="utf-8") as f:
data = json.load(f)
self.title_text = data.get("title", "测验")
self.time_limit = data.get("time_limit", 600)
self.questions = data.get("questions", [])
except FileNotFoundError:
messagebox.showerror("错误", "找不到 quiz_data.json,请确认题库文件存在。")
self.questions = []
QuizApp 类负责整个程序的逻辑调度。构造函数中设置了窗口属性与配色常量,加载题库后自动展示欢迎页。
4.2 欢迎页
def show_welcome(self):
self.clear_frame()
self.welcome_frame = tk.Frame(self.root, bg=self.COLOR_BG)
self.welcome_frame.pack(expand=True)
tk.Label(
self.welcome_frame, text=self.title_text,
font=("微软雅黑", 24, "bold"), bg=self.COLOR_BG, fg=self.COLOR_PRIMARY
).pack(pady=(60, 20))
tk.Label(
self.welcome_frame,
text=f"共 {len(self.questions)} 道题 · 限时 {self.time_limit // 60} 分钟",
font=("微软雅黑", 13), bg=self.COLOR_BG, fg="#666666"
).pack(pady=(0, 10))
rules = [
"· 题目从题库中随机抽取,顺序打乱",
"· 每题计时,超时将自动跳过",
"· 作答后不可返回修改",
"· 提交后即时显示成绩与解析"
]
for rule in rules:
tk.Label(
self.welcome_frame, text=rule,
font=("微软雅黑", 11), bg=self.COLOR_BG, fg=self.COLOR_TEXT
).pack(anchor="w", padx=180)
tk.Button(
self.welcome_frame, text="开始测验", command=self.start_quiz,
font=("微软雅黑", 14, "bold"), bg=self.COLOR_PRIMARY, fg="white",
activebackground="#3a7bc8", activeforeground="white",
relief="flat", padx=40, pady=10, cursor="hand2"
).pack(pady=(30, 0))
def clear_frame(self):
for widget in self.root.winfo_children():
widget.destroy()
欢迎页展示测验标题、题目数量和规则说明,点击「开始测验」按钮后进入答题阶段。
4.3 答题页与计时器
def start_quiz(self):
# 打乱题目顺序
self.shuffled_indices = list(range(len(self.questions)))
random.shuffle(self.shuffled_indices)
self.current_q = 0
self.score = 0
self.user_answers = []
self.time_left = self.time_limit
self.clear_frame()
self.build_question_ui()
self.start_timer()
def build_question_ui(self):
self.clear_frame()
if self.current_q >= len(self.shuffled_indices):
self.show_result()
return
idx = self.shuffled_indices[self.current_q]
q = self.questions[idx]
# 顶部信息栏
top_bar = tk.Frame(self.root, bg=self.COLOR_PRIMARY, height=45)
top_bar.pack(fill="x")
tk.Label(
top_bar, text=f"第 {self.current_q + 1} / {len(self.shuffled_indices)} 题",
font=("微软雅黑", 12, "bold"), bg=self.COLOR_PRIMARY, fg="white"
).pack(side="left", padx=20, pady=8)
self.timer_label = tk.Label(
top_bar, text="", font=("微软雅黑", 12, "bold"),
bg=self.COLOR_PRIMARY, fg="white"
)
self.timer_label.pack(side="right", padx=20, pady=8)
# 题目卡片
card = tk.Frame(self.root, bg=self.COLOR_CARD, bd=1, relief="solid")
card.pack(fill="both", padx=30, pady=20, expand=True)
tk.Label(
card, text=q["question"], wraplength=600, justify="left",
font=("微软雅黑", 14), bg=self.COLOR_CARD, fg=self.COLOR_TEXT
).pack(anchor="w", padx=25, pady=(25, 20))
# 选项
self.option_vars = []
self.option_buttons = []
options_frame = tk.Frame(card, bg=self.COLOR_CARD)
options_frame.pack(padx=25, pady=(0, 10))
for i, opt in enumerate(q["options"]):
var = tk.IntVar(value=-1)
self.option_vars.append(var)
rb = tk.Radiobutton(
options_frame, text=f"{chr(65+i)}. {opt}", variable=var,
value=i, font=("微软雅黑", 12), bg=self.COLOR_CARD,
fg=self.COLOR_TEXT, anchor="w", width=50,
selectcolor=self.COLOR_CARD,
activebackground=self.COLOR_CARD
)
rb.pack(anchor="w", pady=3)
self.option_buttons.append(rb)
# 提交按钮
tk.Button(
card, text="提交答案", command=lambda: self.submit_answer(q),
font=("微软雅黑", 13, "bold"), bg=self.COLOR_PRIMARY, fg="white",
activebackground="#3a7bc8", activeforeground="white",
relief="flat", padx=30, pady=8, cursor="hand2"
).pack(pady=(15, 25))
def start_timer(self):
def update_timer():
if self.time_left <= 0:
messagebox.showinfo("时间到", "测验时间已结束,将自动提交。")
self.show_result()
return
mins, secs = divmod(self.time_left, 60)
self.timer_label.config(text=f"剩余 {mins:02d}:{secs:02d}")
self.time_left -= 1
self.timer_id = self.root.after(1000, update_timer)
update_timer()
def submit_answer(self, q):
if self.timer_id:
self.root.after_cancel(self.timer_id)
selected = -1
for var in self.option_vars:
if var.get() != -1:
selected = var.get()
break
if selected == -1:
messagebox.showwarning("提示", "请先选择一个选项再提交。")
self.start_timer()
return
correct = q["answer"]
is_correct = (selected == correct)
if is_correct:
self.score += 1
self.user_answers.append({
"question": q["question"],
"user_choice": selected,
"correct": correct,
"is_correct": is_correct,
"options": q["options"]
})
self.current_q += 1
self.build_question_ui()
if self.current_q < len(self.shuffled_indices):
self.start_timer()
答题页逐题展示,用户选择后点击提交进入下一题。全局计时器贯穿整个测验,超时自动提交。
4.4 结果页
def show_result(self):
if self.timer_id:
self.root.after_cancel(self.timer_id)
self.clear_frame()
total = len(self.shuffled_indices)
result_frame = tk.Frame(self.root, bg=self.COLOR_BG)
result_frame.pack(expand=True, fill="both")
# 成绩展示
tk.Label(
result_frame, text="测验完成", font=("微软雅黑", 22, "bold"),
bg=self.COLOR_BG, fg=self.COLOR_PRIMARY
).pack(pady=(30, 10))
score_color = self.COLOR_CORRECT if self.score >= total * 0.6 else self.COLOR_WRONG
tk.Label(
result_frame, text=f"{self.score} / {total}",
font=("微软雅黑", 36, "bold"), bg=self.COLOR_BG, fg=score_color
).pack(pady=(0, 5))
tk.Label(
result_frame,
text=f"正确率 {self.score / total * 100:.1f}%",
font=("微软雅黑", 13), bg=self.COLOR_BG, fg="#666666"
).pack(pady=(0, 20))
# 解析列表
canvas = tk.Canvas(result_frame, bg=self.COLOR_BG, highlightthickness=0)
scrollbar = tk.Scrollbar(result_frame, orient="vertical", command=canvas.yview)
scroll_frame = tk.Frame(canvas, bg=self.COLOR_BG)
scroll_frame.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.create_window((0, 0), window=scroll_frame, anchor="nw", tags="frame")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True, padx=(30, 0))
scrollbar.pack(side="right", fill="y")
# 绑定鼠标滚轮
def on_mousewheel(event):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
canvas.bind_all("", on_mousewheel)
for i, ans in enumerate(self.user_answers):
icon = "✓" if ans["is_correct"] else "✗"
color = self.COLOR_CORRECT if ans["is_correct"] else self.COLOR_WRONG
item_frame = tk.Frame(scroll_frame, bg=self.COLOR_CARD, bd=1, relief="solid")
item_frame.pack(fill="x", pady=5, padx=(0, 30))
tk.Label(
item_frame, text=f"{icon} 第{i+1}题",
font=("微软雅黑", 11, "bold"), bg=self.COLOR_CARD, fg=color
).pack(anchor="w", padx=12, pady=(8, 2))
tk.Label(
item_frame, text=ans["question"], wraplength=550,
font=("微软雅黑", 11), bg=self.COLOR_CARD, fg=self.COLOR_TEXT,
justify="left"
).pack(anchor="w", padx=12)
tk.Label(
item_frame,
text=f"你的答案:{ans['options'][ans['user_choice']]} 正确答案:{ans['options'][ans['correct']]}",
font=("微软雅黑", 10), bg=self.COLOR_CARD, fg="#888888"
).pack(anchor="w", padx=12, pady=(2, 8))
# 底部按钮
btn_frame = tk.Frame(result_frame, bg=self.COLOR_BG)
btn_frame.pack(pady=15)
tk.Button(
btn_frame, text="重新测验", command=self.start_quiz,
font=("微软雅黑", 12, "bold"), bg=self.COLOR_PRIMARY, fg="white",
activebackground="#3a7bc8", activeforeground="white",
relief="flat", padx=25, pady=8, cursor="hand2"
).pack(side="left", padx=10)
tk.Button(
btn_frame, text="返回首页", command=self.show_welcome,
font=("微软雅黑", 12), bg="#cccccc", fg=self.COLOR_TEXT,
activebackground="#bbbbbb", relief="flat", padx=25, pady=8, cursor="hand2"
).pack(side="left", padx=10)
# 解除鼠标滚轮绑定,避免影响其他页面
canvas.bind_all("", lambda e: None)
if __name__ == "__main__":
root = tk.Tk()
app = QuizApp(root)
root.mainloop()
结果页展示总分、正确率,并以卡片列表形式逐题给出正误判断与正确答案。用户可以选择重新测验或返回首页。
5. 运行与测试
确保 main.py 与 quiz_data.json 在同一目录下,执行以下命令启动程序:
python main.py
程序启动后将看到欢迎页,点击「开始测验」即可逐题作答。测试要点:
- 验证题目随机打乱是否生效;
- 确认不选选项时提交会弹出提示;
- 检查计时器在最后一秒是否触发自动提交;
- 观察结果页的解析列表是否正确滚动。
6. 打包为 EXE
程序调试无误后,使用 PyInstaller 将其打包成单个 EXE 文件,方便在没有 Python 环境的电脑上直接运行。
6.1 基本打包命令
pyinstaller --onefile --windowed --name "AI编程教育测验" main.py
参数说明:
- --onefile:将所有依赖打包进单个 EXE,分发更简洁;
- --windowed:运行时不弹出命令行窗口(纯 GUI 体验);
- --name:指定生成的 EXE 文件名。
6.2 处理外部数据文件
quiz_data.json 不会被自动打包进 EXE,需要手动指定或运行时动态读取。推荐使用 --add-data 参数:
pyinstaller --onefile --windowed --name "AI编程教育测验" --add-data "quiz_data.json;." main.py
同时修改 main.py 中 load_questions 方法的文件读取路径,使其兼容打包后的运行环境:
def load_questions(self):
# 兼容 PyInstaller 打包后的资源路径
if getattr(sys, "frozen", False):
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(os.path.abspath(__file__))
json_path = os.path.join(base_path, "quiz_data.json")
try:
with open(json_path, "r", encoding="utf-8") as f:
data = json.load(f)
self.title_text = data.get("title", "测验")
self.time_limit = data.get("time_limit", 600)
self.questions = data.get("questions", [])
except FileNotFoundError:
messagebox.showerror("错误", f"找不到题库文件:{json_path}")
self.questions = []
记得在文件顶部添加 import sys。打包完成后,在 dist 文件夹中找到生成的 EXE,双击即可运行。
6.3 自定义图标
如果你希望 EXE 拥有专属图标,准备一个 .ico 格式的图标文件,在打包命令中加入 --icon 参数:
pyinstaller --onefile --windowed --name "AI编程教育测验" --add-data "quiz_data.json;." --icon "icon.ico" main.py
7. 优化建议
以上已经是一个功能完整的测验软件,但你还可以进一步扩展:
- 多题库切换:在欢迎页增加下拉菜单,让用户选择不同科目的题库;
- 成绩记录:将每次测验的成绩保存到本地文件或 SQLite 数据库,提供历史趋势图表;
- 错题本:单独列出做错的题目供复习,支持重做错题模式;
- 题型扩展:增加填空题、判断题、多选题等多种题型;
- 界面美化:使用 ttk 主题或引入图片背景,让 UI 更具吸引力。
8. 总结
本文从零开始,带领你完成了一个基于 Python + Tkinter 的教育测验软件。核心内容包括:
- 用 JSON 管理题库数据,实现题目与代码的解耦;
- 用 Tkinter 构建欢迎页、答题页、结果页三个模块,并实现随机抽题与全局计时;
- 用 PyInstaller 将程序打包为独立 EXE,方便分发部署。
整个项目代码量不到 300 行,却覆盖了界面开发、事件处理、数据持久化和程序打包等实用技能。无论你是教育从业者还是编程初学者,都可以在此基础上二次开发,做出满足自己需求的练习软件。动手试试吧!
更多推荐



所有评论(0)