尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python+Tkinter+SQLite构建智能英语学习系统:记忆曲线与错题闭环设计
英语学习类的毕设年年都有但真正能把“智能”两个字落到实处的并不多。今天要拆解的这套“基于Python的智能英语学习系统”源码包编号65438属于那种看着不花哨、但功能完整、答辩稳的项目用户注册登录、词库管理、记忆曲线复习调度、四种题型测试、错题收集、学习进度可视化一应俱全。项目用Python Tkinter SQLite实现对刚接触Python的毕设党非常友好对想做一个完整桌面项目的开发者来说也是一套可以快速二次开发的模板。这套系统最值得学的不是某一个炫技功能而是“如何把一个需求完整地拆成模块、定好数据表、跑通闭环”。很多同学写毕设最大的问题不是代码不会写而是不知道从哪里下手、表怎么建、模块怎么划分、复习逻辑怎么落地。这篇文章就按我当时开发这套系统的完整思路从设计、建模、编码到踩坑复盘全部过一遍。1. 项目概述与整体设计思路1.1 这个系统要解决什么问题先说痛点。大学英语学习最常见的状态是手机里装了三四个背单词App真正坚持下来的没几个单词背了又忘忘了又背完全靠临时突击学习效果没有任何数据反馈不知道自己这周进步了还是原地踏步。这套系统把这些问题转成了几个具体功能点每日学习任务不仅加入新单词还能自动安排到期复习词不用自己记着哪天该复习哪一批。掌握度跟踪每个单词都有自己的熟练度阶段从“刚认识”到“长期记住”状态全部入库。测试闭环背完立刻测四种题型混合出题答错的自动进错题本后面可以单独重测。可视化反馈学习趋势折线图、正确率饼图、各模块完成度一眼看到自己的学习情况。也就是说“智能”主要体现在三个地方懂遗忘规律、懂用户薄弱点、懂学习数据。这个定位在做需求分析时非常好写也方便答辩时讲清楚创新点。1.2 技术选型为什么是Python Tkinter SQLite毕设技术选型有一条铁律不要选你hold不住的技术栈。当时对比过三套方案方案界面数据库部署复杂度答辩风险Python Tkinter SQLite桌面GUI自带文件型数据库极低装Python即可跑低逻辑透明Python Flask MySQLWeb界面需要装数据库服务中要启动服务中前端工作量翻倍Java Swing MySQL桌面GUI需要装数据库服务中JDBC配置麻烦中环境易出问题最终选了第一个组合理由很实际Tkinter是Python标准库自带的GUI工具不需要额外安装打包时体型也可控。PyQt虽然界面更精致但信号槽机制、资源编译对新手不友好代码一复杂很容易把自己绕晕。毕设要的是“稳定跑起来”不是“界面赛过商业软件”。SQLite更是省心。它就是一个单文件数据库不需要单独安装MySQL、不需要配置账号密码。代码里写好建表语句第一次运行自动生成数据库文件。两台电脑演示也不用搬数据库数据库文件拷过去就能用。至于为什么不用Web方案核心原因是答辩现场的不可控因素太多端口被占用、浏览器缓存、前端样式错乱任何一个问题都可能让演示卡壳。桌面版一个main.py点开就完事风险最小。1.3 功能结构总览系统的功能模块按“学习闭环”来拆一共六个模块核心功能对应数据表用户模块注册、登录、密码加密存储users单词模块词库管理、浏览、按难度筛选words学习模块今日新词到期复习词调度user_word_status测试模块四种题型抽题、判分、错题收集test_records, wrong_records生词本模块错词重测、自动收集wrong_records统计模块学习趋势、正确率、词量统计study_log架构上严格分了三层界面层只负责展示和接收操作业务层处理学习调度、抽题逻辑、统计计算数据层只做SQL读写。这样拆的好处是出问题好排查而且文档里“系统架构设计”那一章写起来非常漂亮。2. 核心功能拆解与数据库建模2.1 数据表设计与关系数据库设计是这套系统最值得讲的部分。表建得好后面所有逻辑都顺畅表建得随意学到一半就发现缺字段、要返工。我当时建了6张表核心结构如下CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, created_at TEXT DEFAULT (datetime(now, localtime)) ); CREATE TABLE words ( id INTEGER PRIMARY KEY AUTOINCREMENT, word TEXT UNIQUE NOT NULL, phonetic TEXT, definition TEXT NOT NULL, example_sentence TEXT, difficulty INTEGER DEFAULT 3 ); CREATE TABLE user_word_status ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, word_id INTEGER NOT NULL, stage INTEGER DEFAULT 0, -- 记忆阶段 0~5 review_count INTEGER DEFAULT 0, last_review_date TEXT, next_review_date TEXT, is_favorite INTEGER DEFAULT 0, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (word_id) REFERENCES words(id) ); CREATE TABLE test_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, word_id INTEGER NOT NULL, question_type INTEGER NOT NULL, -- 1选择 2拼写 3英译汉 4汉译英 is_correct INTEGER NOT NULL, test_time TEXT DEFAULT (datetime(now, localtime)) ); CREATE TABLE wrong_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, word_id INTEGER NOT NULL, wrong_count INTEGER DEFAULT 1, last_wrong_time TEXT, UNIQUE(user_id, word_id) ); CREATE TABLE study_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, study_date TEXT NOT NULL, new_words INTEGER DEFAULT 0, review_words INTEGER DEFAULT 0, total_time INTEGER DEFAULT 0 );几个容易忽略的设计细节user_word_status是用户和单词的关联表这是学习进度的核心。每个用户对每个单词都有自己的状态不能直接改words表否则就是所有用户共用一套进度了。stage字段记录了当前记忆阶段从0到5配合复习间隔算法使用。wrong_records用了UNIQUE(user_id, word_id)约束同一个单词只保留一条错题记录错次数累加。这样做统计的时候非常方便。所有时间字段统一用文本格式YYYY-MM-DD虽然不如时间戳精确但阅读和调试都方便毕设完全够用。2.2 记忆复习调度原理这是整套系统“智能”的核心也是答辩时最有说头的地方。原理来自艾宾浩斯遗忘曲线人脑遗忘不是匀速的学完20分钟后会忘掉约42%一小时后忘掉约56%之后遗忘速度逐渐变慢。所以复习安排应该“先密后疏”。落地到代码里就变成一组递增的复习间隔REVIEW_INTERVALS [1, 2, 4, 7, 15, 30] def calc_next_review_date(stage): 根据当前记忆阶段计算下一次复习日期 if stage len(REVIEW_INTERVALS): return None # 已到最终阶段进入长期记忆 return date.today() timedelta(daysREVIEW_INTERVALS[stage])每次复习单词时答对了stage加1下一次复习间隔拉长。答错了stage减1最低不减到负数间隔缩短增加复习频率。一个单词走完0到5六个阶段大约跨越59天基本能刻进长时记忆。这套调度逻辑实现起来不到50行但价值和解释力远超标。它让你能在答辩时说清楚“我不是简单做了一个背单词工具而是引入了认知心理学经典的遗忘曲线模型并用阶段式调度把它工程化了。”2.3 智能测试的生成策略测试模块不能简单地随机出几个单词。我做了四类题型选择题给英文单词四个中文释义里选正确项。拼写题显示中文释义用户输入英文拼写。英译汉给英文句子选正确中文含义。汉译英给中文语义选对应的英文单词。干扰项生成是里面最有技巧的如果纯随机抽三个词做干扰项会出现“正确选项很简单、干扰项离谱”的情况测试变得没有区分度。我当时的做法是import random def generate_choices(word_id, words_pool, count4): 生成选择题选项确保干扰项与正确项难度接近 correct_word [w for w in words_pool if w[id] word_id][0] # 优先从同难度级别的词里选干扰项避免一眼排除 same_level [w for w in words_pool if w[id] ! word_id and abs(w[difficulty] - correct_word[difficulty]) 1] if len(same_level) count - 1: distractors random.sample(same_level, count - 1) else: distractors random.sample([w for w in words_pool if w[id] ! word_id], count - 1) choices distractors [correct_word] random.shuffle(choices) return choices同时测试完立刻判分、立刻写库。答错的词自动进错题本下一次学习界面会优先把错题排在前面。这样做的好处是“学-测-补”形成一个闭环系统会持续关注用户最薄弱的单词。3. 从零实现关键代码与实操步骤3.1 环境准备与项目目录规划新手照抄版本地开发环境我只用了最朴素的组合Python 3.9以上版本、Visual Studio Code或者PyCharm Community版。安装第三方库只需要一行命令我建议放到虚拟环境里python -m venv venv venv\Scripts\activate # Windows系统激活虚拟环境 pip install matplotlibmatplotlib是唯一必须装的第三方库用于画统计图。Tkinter是Python自带sqlite3模块也是标准库都不需要额外安装。项目目录我习惯按分层结构组织english_learning_system/ ├── main.py # 程序入口 ├── core/ │ ├── __init__.py │ ├── database.py # 数据库连接和建表 │ ├── auth_service.py # 登录注册 │ ├── study_service.py # 学习调度 │ ├── test_service.py # 测试生成与判分 │ └── stats_service.py # 统计计算 ├── ui/ │ ├── __init__.py │ ├── login_window.py │ ├── main_window.py │ ├── study_page.py │ ├── test_page.py │ └── stats_page.py ├── data/ │ ├── words.db # SQLite数据库(首次运行自动生成) │ └── wordbook.csv # 词库原始数据 └── requirements.txt这个结构的好处是数据层代码永远不会混进界面代码。后期如果要换Web界面core部分基本不用动改ui层就行这套减法在论文里也很好写。3.2 登录注册模块密码不能存明文登录注册是每个系统都有的功能但很多毕设直接明文存密码答辩时一问“密码安全怎么考虑”就卡壳了。其实加密存密码非常简单用hashlib就能完成import sqlite3 import hashlib import os class AuthService: def __init__(self, db_path): self.db_path db_path def _get_conn(self): conn sqlite3.connect(self.db_path) conn.row_factory sqlite3.Row return conn def _hash_password(self, password, saltNone): 加盐哈希即使两个用户密码相同哈希值也不一样 if salt is None: salt os.urandom(16).hex() digest hashlib.sha256((salt password).encode()).hexdigest() return salt, digest def register(self, username, password): conn self._get_conn() try: salt, digest self._hash_password(password) conn.execute( INSERT INTO users (username, password_hash) VALUES (?, ?), (username, f{salt}${digest}) ) conn.commit() return True except sqlite3.IntegrityError: return False # 用户名重复 finally: conn.close() def login(self, username, password): conn self._get_conn() row conn.execute( SELECT password_hash FROM users WHERE username ?, (username,) ).fetchone() conn.close() if row is None: return False salt, digest row[password_hash].split($) _, new_digest self._hash_password(password, salt) return digest new_digest有几个细节值得注意密码哈希值即使泄露也无法还原出原始密码。加盐的目的是防止“彩虹表攻击”也就是攻击者预计算出常用密码的哈希表直接比对。数据库连接用短连接用完即关避免桌面程序长时间占用连接导致数据库被锁。答辩时如果被问“数据库安全性怎么保证”你就可以讲哈希、加盐、防暴力破解的思路这已经超出很多同水平毕设的准备了。3.3 单词学习与测试模块实现学习页面的核心逻辑是获取“今日待学词列表”。这个词表由两部分组成新词和到期复习词。class StudyService: def __init__(self, db_path, user_id): self.db_path db_path self.user_id user_id def get_today_words(self, new_word_count8): 获取今日学习列表先排错题再排到期复习词最后补新词 conn sqlite3.connect(self.db_path) conn.row_factory sqlite3.Row today str(date.today()) # 1. 错题优先最多5个 wrong_words conn.execute( SELECT w.* FROM wrong_records wr JOIN words w ON w.id wr.word_id WHERE wr.user_id ? ORDER BY wr.wrong_count DESC LIMIT 5 , (self.user_id,)).fetchall() # 2. 到期复习词 due_words conn.execute( SELECT w.* FROM user_word_status uws JOIN words w ON w.id uws.word_id WHERE uws.user_id ? AND uws.next_review_date IS NOT NULL AND uws.next_review_date ? ORDER BY uws.next_review_date ASC LIMIT ? , (self.user_id, today, new_word_count)).fetchall() # 3. 新词补足 known_ids [r[id] for r in wrong_words] [r[id] for r in due_words] placeholders ,.join(? * len(known_ids)) new_words conn.execute(f SELECT * FROM words WHERE id NOT IN ({placeholders}) ORDER BY difficulty ASC LIMIT ? , known_ids [new_word_count]).fetchall() conn.close() return wrong_words, due_words, new_words这段代码体现了“错题优先 - 复习到期 - 新词补充”的调度优先级这也是“智能”体验的直接体现。用户在界面上点“认识”“模糊”“不认识”对应的处理逻辑分别是stage加1、stage不变、stage减1同时更新复习日期。测试模块的核心是拼写题判分逻辑需要处理大小写和前后空格def check_spelling(user_answer, correct_word): 拼写判分忽略大小写和首尾空格 return user_answer.strip().lower() correct_word.strip().lower()其余三种题型用上面说到的选项生成函数最后提交时统一判分并写test_records表。3.4 学习统计可视化中文乱码是必经之路统计页面用matplotlib绘图然后嵌入Tkinter窗口。嵌入方式是用matplotlib自带的Tkinter后端import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg def draw_study_trend(parent_frame, user_id): 在Tkinter中绘制近30天学习趋势折线图 # 解决中文显示问题 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False dates, new_counts, review_counts stats_service.get_daily_data(user_id) fig, ax plt.subplots(figsize(6, 4)) ax.plot(dates, new_counts, markero, label新学单词) ax.plot(dates, review_counts, markers, label复习单词) ax.set_xlabel(日期) ax.set_ylabel(单词数量) ax.set_title(近30天学习趋势) ax.legend() ax.xaxis.set_major_locator(plt.MaxNLocator(8)) # x轴标签太多会重叠 canvas FigureCanvasTkAgg(fig, masterparent_frame) canvas.draw() canvas.get_tk_widget().pack(fillboth, expandTrue)踩坑点提前说一下matplotlib在Windows下默认字体是英文不设置font.sans-serif的话图里的中文全是方块。axes.unicode_minus必须设成False否则负号会显示成方块。x轴日期太密集时用MaxNLocator限制刻度数量否则整个图糊成一团。同理还可以绘制单词类型占比饼图、每轮测试正确率柱状图。统计页是答辩演示的高光环节数据图一出一看就上档次。4. 实操复盘典型问题与排查技巧4.1 数据库并发与路径问题开发过程中遇到最典型的两个问题第一个是SQLite报“database is locked”。原因是桌面程序频繁打开多个窗口操作数据库如果一个写操作卡住了另一个连接就会抢锁失败。解决办法是我把所有写库操作改成短连接并且用事务提交def record_test_result(self, user_id, word_id, question_type, is_correct): conn sqlite3.connect(self.db_path) with conn: # 用with块自动管理事务 conn.execute( INSERT INTO test_records (user_id, word_id, question_type, is_correct) \ VALUES (?, ?, ?, ?), (user_id, word_id, question_type, is_correct) ) conn.close()第二个问题是相对路径bug。如果代码用了open(words.db)这种相对路径双击源码运行时工作目录不同数据库文件会被创建到意想不到的位置。我的处理方式是用Path(__file__)定位到项目根目录再拼出数据库绝对路径from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent DB_PATH BASE_DIR / data / words.db这样不管在哪个目录启动程序数据库都稳稳定定在项目data文件夹里。4.2 Tkinter界面卡顿与布局问题Tkinter的派克布局pack/grid有个规矩同一个父容器里不能混用两种布局方式。我有一次在主窗口里pack了侧边栏又用grid放统计图结果按钮歪得看不出是按钮。这是一个新手必踩坑答案也很简单一个容器只选一种布局如果确实需要表格网格再用Frame拆一层。另一个问题是事件回调的变量引用陷阱。在循环里创建多个按钮直接写commandself.delete_word(word_id)括号一加函数在创建时就被调用了点击按钮反而没反应。正确写法是用lambda参数绑定btn tk.Button(..., commandlambda widword_id: self.delete_word(wid))初始化阶段这个bug排查了快一个小时就是因为没注意Python闭包的延后绑定特性。界面卡顿则几乎都与循环里做了耗时操作有关。比如给Treeview控件插入几百行数据时用循环逐行insert会很慢正确做法是拼成数组一次性insert_all_item。如果还需要响应式刷新记得用root.after()而不是time.sleep()后者会把整个界面冻结。4.3 PyInstaller打包与运行环境答辩通常要求在别人电脑上也能运行这就绕不开打包。我用的PyInstallerpip install pyinstaller pyinstaller -F -w -i assets/icon.ico main.py参数含义-F打成单文件exe-w不显示控制台黑窗口-i指定图标打包后最容易踩的坑是运行时报“No module named matplotlib.backends.backend_tkagg”。明明本地运行好好的打包完就缺模块。这是因为PyInstaller静态扫描时漏掉了matplotlib动态导入的后端。解决办法有两种在代码里显式from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg让扫描器能检测到。不行就在打包命令里加pyinstaller -F -w --hidden-import matplotlib.backends.backend_tkagg main.py还有一个小经验打包出来的exe体积一般在30到50MB主要是matplotlib拖大的。如果实在介意体积可以把统计图的matplotlib换成用FigureCanvasTkAgg之外的方式或者改用PIL画简单柱状图但我个人觉得为几十MB去折腾不值稳定优先。4.4 答辩与演示实用建议源码包编号65438对应的这套系统答辩时长一般控制在8到10分钟。我的演示顺序建议是注册新账号30秒展示UI完整。进入学习页完成3到5个单词学习展示调度逻辑入口。快速做一轮测试故意答错一题展示判分、错题收集。切到生词本展示刚刚的错题已经自动出现这里一步就能说明系统的数据联动。打开统计页展示趋势图和正确率图重点停留图最能撑场面。演示用的账号建议提前跑一周数据比如每天学习8个新词、复习20个词。答辩当天统计页会有一整页漂亮的曲线而不是今天的孤零零几个点。关于答辩提问最常被问到的几个我提前备了答案为什么用SQLite不用MySQL答面向单用户、免部署、数据文件可迁移原型阶段压缩环境成本。记忆曲线怎么实现的答阶段递增复习间隔0到5共6个阶段答错回退。系统能不能扩展答core层与ui层分离更换Web界面无需改业务逻辑词库可批量导入。整理下来这套系统代码量不大核心逻辑加起来也就一千多行但是模块清晰、闭环完整、有算法支撑作为本科毕设属于性价比很高的一档。源码包编号65438里已含词库、数据库初始化脚本和完整项目代码。我个人建议拿到源码后不要直接提交至少改一个小地方比如把词库换成四六级核心词表或者加个“每日打卡提醒”的小按钮。一个小小的个性化改动就能让答辩老师在源码检查时第一眼看到你的工作痕迹而不只是从网上下载的模板。
RELATED

相关推荐

最简单的 Claude Skill 入门教程:只需 5 步,无需配置环境,小白也能学会(TaoToken 版)

最简单的 Claude Skill 入门教程:只需 5 步,无需配置环境,小白也能学会(TaoToken 版)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/10 14:47:33
如何让 agent 禁止访问的某些文件夹呢:用 aiignore 白名单给 OpenCode 划边界

如何让 agent 禁止访问的某些文件夹呢:用 aiignore 白名单给 OpenCode 划边界

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/10 14:47:33
Python类型判断实战:type、isinstance与鸭子类型详解

Python类型判断实战:type、isinstance与鸭子类型详解

写代码这几年,尤其是啃“鱼书”《深度学习入门:基于Python的理论与实现》那段时期,我发现很多新手卡住的地方根本不是神经网络反向传播,反而是“这个变量到底是什么类型”这种最基础的问题。明明照着书敲,结果一跑就报…

📅 2026/10/10 14:47:33
MORE NEWS

更多资讯

📰

STM32F4 OTA升级:单App与双App方案优缺点对比

1. 引言在嵌入式产品开发中,OTA(Over-The-Air)升级已成为一项关键能力。对于基于 STM32F4 系列 MCU 的产品,OTA 升级方案的设计直接关系到系统的稳定性、可靠性和用户体验。目前主流的方案分为单 App 升级和双 App 升级&#xff0…

📰

Flutter插件鸿蒙化实战:链接预览库的抓取与渲染适配全解析

最近在做 Flutter 三方库的鸿蒙化适配,挑了一个非常典型的库来开刀:simple_link_preview。这个库的名字听起来简单,干的活却很核心——网页链接的智能抓取与卡片渲染。你在社交应用里发一条链接,它帮你自动抓出标题、摘要、缩略图…

📰

Unity3D轮播图实战:ScrollRect回中、无限轮播与自动播放全解析

简介:一份基于Unity的UGUI ScrollRect组件实现的轮播图功能源码,用于弥补UGUI系统未提供现成轮播组件的缺憾,适用于游戏主界面、活动宣传页、商品展示等常见场景,面向具备一定Unity基础、需要集成轮播效果的开发者。资源包共408个…

📰

企业内部 AI 网关怎么选?LiteLLM、New API、OctaFuse 三方横评

企业内部 AI 网关怎么选?LiteLLM、New API、OctaFuse 三方横评 【免费下载链接】litellm The fastest, litest AI Gateway. Rust core with Python SDK. Call 100 LLM APIs in OpenAI (or native) format with cost tracking, guardrails, load balancing, and logg…

📰

Qwen-Image-2.1多图参考编辑详解:如何用10张参考图生成合影与全身穿搭

【免费下载链接】Qwen-Image-2.1 Qwens most powerful open-source image generation model 项目地址: https://gitcode.com/gh_mirrors/qw/Qwen-Image-2.1 点击查看 免费下载 Qwen-Image-2.1 是通义千问团队开源的文生图生成与图像编辑模型,最大亮点是…

📰

TM4C129ENCPDT与PCA9422协同电源管理设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬