ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用Python打造自己的英语教学软件:从零实现背单词工具

用Python打造自己的英语教学软件:从零实现背单词工具 背单词这件事几乎人人都有几段放弃史。手机里的背词App装了一堆免费额度用完就卸载付费功能开了又关最后真正能坚持下来的方式反而是自己动手写一个英语教学软件。这一节的案例就是把我日常背词的完整流程做成代码单词管理、发音朗读、四选一测验、错题统计、复习提醒全程用Python从零实现。项目本身并不复杂但对零基础的读者来说它刚好能把Python的sqlite3数据库、gtts语音合成、pygame音频播放、random调度这些知识点串成一条线。做完之后你得到的不仅是一个能跑的英语学习小工具更是一套理解数据怎么存、功能怎么分、逻辑怎么串的完整思路。这篇博文会把每一步的取舍讲透跟着敲完之后你再去看其他小案例会发现自己已经能看懂大半了。1. 先想清楚一件事这个英语教学软件到底要做什么动手写代码之前我习惯先画一张功能边界图。很多新手一上来就想做像百词斩一样的产品结果被UI、账号系统、云端同步这些巨型需求压垮没写几百行就放弃了。1.1 从背单词的日常场景倒推功能回想一下你平时怎么背单词打开App先学一组新词能看到单词、音标、释义、例句然后听发音接着做几道选择题测一测答对的单词过几天再复习一遍答错的单词第二天立刻重现。这个流程听起来很朴素但它就是一个英语教学软件的完整闭环词库存储维护一组带释义和例句的单词支持增删改查。学习新词按顺序取出新词展示释义和例句并朗读发音。测验练习给出单词让你从四个选项中选出正确释义或反过来给出释义选单词。错题与复习答错的单词进错题本答对的单词按间隔日期安排复习。统计反馈简单记录今天学了几个、答对几个给学习者一点正反馈。我不建议一上来就做图形界面。零基础阶段先做一个命令行交互版本把业务逻辑跑通比什么都重要。界面的价值是锦上添花不是雪中送炭。等你确定逻辑没问题了再套一个tkinter外壳或者干脆转成Web服务那是后话。1.2 本项目的技术选型为什么这么选选型这件事说到底是够用就好。我在做这个项目时反复问自己这个功能用Python标准库能不能做能就不装第三方包。这样做的原因有三第一依赖越少部署越简单。你写的小工具不用装一堆乱七八糟的环境拷到另一台电脑就能跑这对初学者建立信心特别重要。第二标准库是学习Python最好的教材。sqlite3怎么写SQL、random怎么取随机样本、datetime怎么算日期这些都是在真实项目中练出来的。第三踩坑点可控。第三方库版本冲突是初学者最容易崩溃的根源而标准库基本不存在这个问题。具体到本项目的选型我的方案是这样功能选用方案理由数据存储SQLite标准库sqlite3单文件即数据库无需额外服务零基础友好单词分类文件列表词库导入用简单文本格式避免一开始就写复杂解析发音朗读gTTS生成mp3 pygame播放发音自然、安装简单离线备用方案用pyttsx3测验随机random模块学生词选项和打乱顺序都靠它复习调度简化版间隔重复算法用熟读度日期计算不引入Anki那套复杂算法这套组合看起来不起眼但它的优点是每个模块都能独立替换。比如你嫌gTTS发音太机械换一个本地语音引擎就行不影响其他代码。2. 零基础起手式环境准备与项目骨架搭建环境准备这块很多人会栽在第一步。我见过太多同学卡在我按教程装了Python但是为什么打开不是那个界面之类的问题上。这里我尽量说清楚。2.1 Python环境与第三方库的最小安装如果你还没装Python去 python.org 下载对应系统的最新稳定版安装包安装时务必勾选Add Python to PATH。这一步不勾后面在命令行里敲python就会提示找不到命令很多新手莫名卡住就是卡在这。装好之后建议顺手验证一下python --version能打印出版本号就说明环境OK。接下来装第三方库。这个案例只用三个包量很小pip install gtts pygamepyttsx3是离线备用方案局域网环境或者机器上没有音频输出时可以装不装也不影响主流程。装好之后我建议立刻做一个最小化验证就是简单地试一下导入能不能成功python -c import gtts, pygame; print(ok)能打印ok再继续往下写这一步能帮你把环境问题和代码问题彻底隔离后面出bug时排查范围小很多。2.2 项目目录结构一开始就分好层零基础写小工具最容易犯的错是把所有代码塞进一个main.py。不是说不行但项目一旦超过三四百行维护起来就会非常痛苦。我习惯一开始就把目录分好哪怕前期多花五分钟。一个适合这个项目的目录结构如下english_teacher/ ├── main.py # 主循环命令行交互入口 ├── database.py # 数据库连接与建表 ├── words_manager.py # 词库增删改查 ├── quiz_engine.py # 测验出题与判分 ├── voice.py # 发音模块gTTS pyttsx3 双方案 ├── scheduler.py # 间隔复习调度 ├── words.txt # 初始词库文件 └── english.db # SQLite数据库运行时自动生成每个文件只干一件事。main.py负责流程和交互database.py负责建表和连接words_manager.py负责查词和更新quiz_engine.py负责出题判分voice.py负责说话scheduler.py负责算复习日期。这一步看起来是多此一举但对后续迭代价值很大。比如你某天想改进发音只需要改voice.py不用担心把测验逻辑搞乱想出新的题型只需要在quiz_engine.py里加函数。模块之间通过简单的函数接口连接彼此不碰对方的内部实现。2.3 用SQLite建单词表数据库没那么吓人很多零基础同学一听到数据库三个字就发怵其实SQLite就是一个存在你硬盘上的单文件数据库不需要安装任何服务器软件。对这个英语教学软件而言我只需要一张单词表和一张学习记录表就能撑起全部功能。先看database.py的核心逻辑import sqlite3 DB_PATH english.db def get_connection(): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn def init_db(): conn get_connection() cursor conn.cursor() # 单词表 cursor.execute( CREATE TABLE IF NOT EXISTS words ( id INTEGER PRIMARY KEY AUTOINCREMENT, word TEXT NOT NULL, meaning TEXT NOT NULL, example TEXT DEFAULT , example_zh TEXT DEFAULT , proficiency INTEGER DEFAULT 0, next_review TEXT DEFAULT ) ) # 学习记录表 cursor.execute( CREATE TABLE IF NOT EXISTS study_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, word_id INTEGER, result TEXT, ts TEXT DEFAULT CURRENT_TIMESTAMP ) ) conn.commit() conn.close()为什么要点选AUTOINCREMENT和TEXTAUTOINCREMENT让id自动增长这样后续删除某个单词后id不会原地复用复用会导致历史记录串词这是个很隐蔽的坑。next_review TEXT用来存下次复习日期我用YYYY-MM-DD这种字符串格式原因下面调度模块会说不擅长日期运算的初学者用字符串也很直观。这张表设计里proficiency是用来做简化版间隔复习的关键字段初始为0每答对一次加1答错归零。next_review是下次该这个单词出现的日子。这两个字段配合起来就实现了最基础的遗忘曲线。3. 核心功能逐个落地词库、发音、测验、复习调度骨架搭好之后开始填充血肉。这一节是全文的实操核心我按照数据 → 媒体 → 交互 → 策略的顺序一层层把功能做出来。3.1 词库导入与单词管理词库是英语教学软件的燃料。没有好词库一切功能都白搭。我准备了一个纯文本的初始词库words.txt格式很简单每行一个单词字段用竖线分隔apple|苹果|I eat an apple every day.|我每天吃一个苹果。 abandon|放弃|He abandoned the plan.|他放弃了这个计划。 brave|勇敢的|She is brave enough to speak in public.|她足够勇敢能在公众面前发言。这样设计的理由很朴素用记事本就能编辑替换成语料文件只需要改文本。等以后你想导入四六级词表、考研核心词写一个解析器把Excel或CSV转成这种格式就行。words_manager.py里实现两个核心函数一个把文本导入数据库一个返回待学/待复习的单词。import sqlite3 from database import get_connection def import_words_from_file(filepath): conn get_connection() cursor conn.cursor() with open(filepath, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue parts line.split(|) if len(parts) 2: continue word parts[0].strip() meaning parts[1].strip() example parts[2].strip() if len(parts) 2 else example_zh parts[3].strip() if len(parts) 3 else # 避免重复导入 cursor.execute(SELECT id FROM words WHERE word ?, (word,)) if cursor.fetchone() is None: cursor.execute( INSERT INTO words (word, meaning, example, example_zh) VALUES (?, ?, ?, ?) , (word, meaning, example, example_zh)) conn.commit() conn.close()这里有一个新手容易踩的点重复导入问题。我事先查一次SELECT存在就跳过。不加这个判断每跑一次脚本词库就翻一倍。查询函数则负责把今天该学的单词拎出来规则很简单next_review为空从未学过或者next_review小于等于今天日期。SQL写出来就是def get_words_for_today(limit10): conn get_connection() cursor conn.cursor() today datetime.now().strftime(%Y-%m-%d) cursor.execute( SELECT * FROM words WHERE next_review OR next_review ? ORDER BY proficiency ASC, id ASC LIMIT ? , (today, limit)) rows cursor.fetchall() conn.close() return rowsORDER BY proficiency ASC的作用是把熟练度低的单词排在前面保证生词优先出现。这个排序逻辑是复习调度的关键千万不能省。3.2 让单词开口说话gTTS发音模块发音功能算是这个项目最有教学软件感觉的部分。用gtts生成MP3再用pygame播放原理简单Google的文本转语音接口把英文单词转成音频文件然后播放器播出来。但这里有个必须处理的坑gtts要联网。离线环境下一调用就超时初学者很容易被搞懵。所以我做成了双方案def speak(word, enginegtts): if engine gtts: _speak_gtts(word) else: _speak_local(word) def _speak_gtts(word): import os import tempfile import pygame from gtts import gTTS tts gTTS(textword, langen) with tempfile.NamedTemporaryFile(suffix.mp3, deleteFalse) as tmp: tmp_path tmp.name tts.save(tmp_path) pygame.mixer.init() pygame.mixer.music.load(tmp_path) pygame.mixer.music.play() while pygame.mixer.music.get_busy(): pygame.time.wait(100) pygame.mixer.quit() os.unlink(tmp_path) def _speak_local(word): import pyttsx3 engine pyttsx3.init() engine.say(word) engine.runAndWait()重点说两个实操细节。第一临时文件的清理。用tempfile.NamedTemporaryFile生成一个临时路径播放完毕后用os.unlink删掉。如果不删每次背单词都会在系统临时目录留下一个MP3时间长了堆积垃圾文件。第二pygame的初始化时机。pygame.mixer.init()在每播放一次语音时重新初始化pygame.mixer.quit()在播完后再关掉。这样做的好处是避免和其他模块抢音频设备但要注意pygame.mixer.init()必须放在加载文件之前否则会无声。如果你在Linux服务器上跑可能还需要先装libportaudio2之类的系统依赖Windows和macOS一般不需要。我自己在Windows上实测gTTS方案发音自然度明显好于本地引擎但本地引擎的优势是完全离线、反应快两者各有利弊。3.3 测验引擎四选一怎么出题最合理测验是本项目交互感最强的环节。我采用最常见的看单词选释义题型出题逻辑分成三步从今天学习的单词里随机挑一个作为正确答案从全词库里随机挑三个不同单词作为干扰项把四个选项打乱顺序输出。import random def build_quiz(words, all_words_count50): if len(words) 1: return None correct random.choice(words) # 取干扰项保证选项不重复 candidates [w for w in all_words if w[id] ! correct[id]] distractors random.sample(candidates, min(3, len(candidates))) options distractors [correct] random.shuffle(options) return correct, options这里有个细节新手容易忽略干扰项必须来自正确答案以外的单词。很多人想当然地直接从全词库random.sample(4)结果四个选项可能全是错的测验就失去了意义。出题之外判分逻辑也很简单用户输入1/2/3/4对应选项程序比对答案是否正确然后把结果写入study_records表同时更新单词的proficiency和next_review。def record_answer(word_id, correct, conn): cursor conn.cursor() if correct: cursor.execute(UPDATE words SET proficiency proficiency 1 WHERE id ?, (word_id,)) else: cursor.execute(UPDATE words SET proficiency 0 WHERE id ?, (word_id,)) cursor.execute(INSERT INTO study_records (word_id, result) VALUES (?, ?), (word_id, correct if correct else wrong)) conn.commit()答错归零这个策略是我见过最快见效的间隔重复简化版。它符合认知科学的基本规律一次错误往往意味着记忆断层重新计时比下次继续复习更合理。3.4 复习调度用日期字符串实现简易遗忘曲线完整的遗忘曲线算法比较复杂比如Anki的SM-2但对一个教学小工具来说简化版完全够用。我的策略是按熟练度决定下次复习间隔间隔天数依次递增。from datetime import datetime, timedelta # 熟练度对应的复习间隔天 REVIEW_GAP [1, 2, 4, 7, 15, 30] def calculate_next_review(proficiency): if proficiency len(REVIEW_GAP): proficiency len(REVIEW_GAP) - 1 days REVIEW_GAP[proficiency] return (datetime.now() timedelta(daysdays)).strftime(%Y-%m-%d)为什么用proficiency来索引间隔而不是直接用时间戳因为熟练度本质上是记忆稳定度的代理指标。一个单词从0到5对应着新词→刚记住→短期稳定→长期稳定→熟练→掌握六个阶段。间隔从1天逐步拉长到30天正好符合记忆科学中随着复习次数增加遗忘速度减慢的规律。日期采用YYYY-MM-DD字符串还带来一个额外好处SQLite的字符串比较和日期比较是完全一致的。所以next_review 2026-01-01这种写法是可行的不需要转换函数零基础同学理解起来更直观。不过要说明的是这个简化版有一个明显不足它把所有答对的单词一视同仁地加熟练度没有考虑这个单词本身有多难。实际使用中像abandon这种词可能连续几次都错说明它对你的难度偏高单纯靠熟练度加减不够灵敏。后续优化方向可以是引入一个难度系数字段把用词频率和错误次数作为权重参与计算。这属于进阶玩法这里先不展开。4. 串起完整学习流程主程序设计与交互打磨模块都写好了最后要写一个main.py把这些零件组装起来。这也是零基础新手最容易忽略的部分模块写了一大堆却不知道如何让它们自然协作。4.1 主循环让学习流程像App一样自然我把主程序设计成一个简单的状态循环用户每次进入程序依次执行今日新词学习 → 发音朗读 → 四选一测验 → 统计展示。伪代码如下def main(): init_db() import_words_from_file(words.txt) while True: print(1. 开始今日学习 2. 查看错题 3. 退出) choice input(请选择) if choice 1: start_daily_lesson() elif choice 2: show_wrong_words() elif choice 3: break注意这里的导入词库和开始学习是分开的。刚开始运行时词库可能为空先导入一次后续再运行也不会重复导入3.1节那个查重逻辑起了作用。start_daily_lesson是整个程序的核心我建议这么写def start_daily_lesson(): conn get_connection() words get_words_for_today(conn, limit10) if not words: print(今天没有需要学习的单词休息一下) conn.close() return for word in words: print(f单词{word[word]} 释义{word[meaning]}) print(f例句{word[example]}) speak(word[word]) # 朗读 input(按回车键继续) correct, options build_quiz([word], conn) guess input(请选择正确释义1-4) is_correct options[int(guess)-1][id] correct[id] record_answer(word[id], is_correct, conn) print(答对了 if is_correct else f错了正确答案是 {correct[meaning]}) conn.close() print(今日学习完成)这里有一个很关键的设计每学完一个单词立刻测验一次而不是等全部学完再统一测。原因是及时测验能形成学习—提取—反馈的闭环记忆效果远好于学完一批再测。这个小细节是很多人会忽略的心理学知识点。4.2 错题本与学习统计让工具给出反馈只有输入和练习没有反馈学习者很快就会失去动力。show_wrong_words函数从study_records里把最近5次答错的单词揪出来再进行一次补考def show_wrong_words(): conn get_connection() cursor conn.cursor() cursor.execute( SELECT w.* FROM words w JOIN study_records r ON w.id r.word_id WHERE r.result wrong GROUP BY w.id ORDER BY MAX(r.id) DESC LIMIT 5 ) wrong_words cursor.fetchall() if not wrong_words: print(没有错题继续保持) else: print(上次答错的单词) for w in wrong_words: print(f{w[word]}{w[meaning]}) conn.close()注意这个SQL用GROUP BY w.id MAX(r.id)效果是按最近一次错误时间排序并去重。如果你直接写ORDER BY r.id DESC不带分组同一个单词会重复出现很多遍体验很糟糕。这个写法是我在调了好几次才发现的新手大概率会踩。统计方面加一个简单函数输出当日数据def show_today_report(): conn get_connection() cursor conn.cursor() cursor.execute(SELECT COUNT(*) FROM study_records WHERE date(ts) date(now)) total cursor.fetchone()[0] cursor.execute(SELECT COUNT(*) FROM study_records WHERE date(ts) date(now) AND result correct) correct cursor.fetchone()[0] print(f今日学习{total}题答对{correct}题正确率{correct/total*100:.1f}%) conn.close()这种每日正确率能给学习者一个简单的进度锚点看到数据在进步坚持的动力就会强很多。4.3 交互细节打磨让零基础用户也不怕黑窗口命令行界面虽然朴素但交互是否友好直接决定这个工具你会不会真的用下去。我优化了几个小点输入异常兜底用户在选1/2/3时可能输字母、空字符串我直接捕获异常并提示重新输入而不是让程序崩溃。步骤提示每步操作都加一句下一步做什么的提示比如按回车键继续减少用户迷茫。视觉分隔每组学习内容之间用一条短横线隔开避免黑窗口里文字混成一团。进度导出我加了一个简单的export_progress()函数把学习记录导出为CSV文件方便自己复盘也可以为后面的学习报告功能打基础。def export_progress(): conn get_connection() cursor conn.cursor() cursor.execute(SELECT * FROM study_records) rows cursor.fetchall() import csv with open(progress.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([id, word_id, result, ts]) writer.writerows(rows)导出CSV这个功能虽然只有短短几行但它让数据拥有了外部可读性——你可以随时打开Excel查看自己背了哪些词错了几个。这种外部反馈对坚持学习的心理暗示作用经常被技术教程忽略。5. 跑起来之后我踩过的坑、调优思路和下一步扩展代码写完了能跑了但离好用还有很长一段路。这一节把我实测中遇到的问题和思考写出来节省你大量排查时间。5.1 发音模块的三大实际问题问题一gTTS网络超时。这个解决方案我前面已经说了双引擎兜底。实测中断网状态下pyttsx3几乎是秒回虽然音质像机器人但起码保证功能不中断。我建议把引擎选择做成配置项比如写进一个config.py里而不是写死在代码中。问题二pygame播放阻塞。while pygame.mixer.music.get_busy()这个循环会让程序卡住直到音频播完。如果一句句子很长界面就仿佛死机了。解决方法是给sentence级语音加一个超时机制超过8秒就跳过。零基础阶段不做也行但要知道有这个问题。问题三音频设备被占用。如果你同时开了音乐播放器pygame.mixer.init()偶尔会报错。现场解决靠捕获异常并回退到静音模式skip_sound标志置为True不让一个发音错误毁掉整轮学习。5.2 数据库设计上的两个隐藏风险sqlite3用起来简单但有两个点新手特别容易踩并发写冲突。运行中如果同时有两个进程打开同一个english.db写入时会报database is locked。我的解法是所有写操作尽快提交并关闭连接程序只在需要时创建连接用完就关。不要保持一个长期打开的连接不放手。编码问题。如果你把english.db拷到Windows并直接用旧版Excel打开汉字可能乱码。数据库本身用UTF-8存储问题多半出在读取工具上。我在导出CSV时显式指定encodingutf-8配套的还有utf-8-sig带BOM后者在Excel里显示中文更友好。5.3 如果还想继续扩展我建议按这个顺序来这个小案例的价值不只是让你背几个单词而是一个可以持续生长的学习基础设施。我的建议扩展顺序是导入真实词库网上搜CET4单词表 txt稍微写个解析脚本几秒钟就能导入几千词瞬间变成一个正经背词工具。答题模式扩展加上看释义选单词的反向题型或者拼写题显示中文用户输入英文难度分层更科学。简易图形界面用tkinter给这个工具套一个GUI壳把单词卡片和选项做成按钮体验会好非常多。数据可视化用matplotlib画一条每日学习量/正确率曲线时间长了你会非常想看到这条曲线。学习计划导出生成一个iCal日历提醒文件让手机系统在设定的复习时间弹通知这就是个人背词助手的雏形了。我自己的做法是先把词库扩到高频词汇1500个然后每天固定学习10个新词、复习20个旧词。这样坚持了一个多月明显感觉到阅读技术文档时遇到的熟词变多了。这个项目最大的收获不是代码量而是用代码把一件需要长期坚持的事情自动化、流程化、反馈化。最后分享一个我在日常使用中的小技巧把main.py的路径做成一个快捷方式放在桌面每天打开电脑第一件事就是运行它。一开始你可能会觉得每天手动敲命令很麻烦但恰恰是这种每天花三分钟的低门槛流程才是让学习习惯真正成型的关键。代码的价值有时候不在于算法有多强而在于你愿意天天用它。
返回列表