Python开发英语记忆单词软件
"""
英语单词记忆软件 - 基于记忆曲线的间隔重复学习系统
支持Markdown格式单词本,手动控制释义显示,自动安排复习计划
"""
import sqlite3
import datetime
import re
import os
import random
from tkinter import *
from tkinter import messagebox, filedialog
from tkinter import ttk
class WordMemoryDB:
"""数据库管理类,处理单词存储和记忆曲线算法"""
def __init__(self, db_path='words.db'):
self.db_path = db_path
self.init_database()
def init_database(self):
"""初始化数据库表结构"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS words (
id INTEGER PRIMARY KEY AUTOINCREMENT,
word TEXT NOT NULL UNIQUE, -- 添加UNIQUE约束防止重复
meaning TEXT,
example TEXT,
repetitions INTEGER DEFAULT 0,
ease_factor REAL DEFAULT 2.5,
interval_days INTEGER DEFAULT 0,
last_review DATE,
next_review DATE
)
''')
conn.commit()
conn.close()
def import_from_markdown(self, md_path):
"""
从Markdown文件导入单词(仅添加新词,不清空原有数据)
预期格式:每个词条由三行组成(单词、中文释义、例句),词条间用空行分隔
返回新增的单词数量
"""
if not os.path.exists(md_path):
raise FileNotFoundError(f"单词本文件不存在: {md_path}")
with open(md_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 清理空行和Markdown标记
cleaned_lines = []
for line in lines:
line = line.strip()
if line: # 跳过空行
# 移除常见的Markdown标记
line = re.sub(r'\*\*(.*?)\*\*', r'\1', line) # 粗体
line = re.sub(r'\*(.*?)\*', r'\1', line) # 斜体
line = re.sub(r'#+', '', line) # 标题标记
line = line.strip()
cleaned_lines.append(line)
# 每三行组成一个词条
words_data = []
for i in range(0, len(cleaned_lines), 3):
if i + 2 < len(cleaned_lines):
word = cleaned_lines[i]
meaning = cleaned_lines[i+1]
example = cleaned_lines[i+2]
words_data.append((word, meaning, example))
# 获取数据库中已存在的单词(用于去重)
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT word FROM words")
existing_words = {row[0] for row in cursor.fetchall()}
# 插入新单词(去重)
today = datetime.date.today().isoformat()
new_count = 0
for word, meaning, example in words_data:
if word not in existing_words:
try:
cursor.execute('''
INSERT INTO words (word, meaning, example, last_review, next_review)
VALUES (?, ?, ?, ?, ?)
''', (word, meaning, example, today, today))
new_count += 1
existing_words.add(word) # 更新已存在集合,避免本次导入中的重复
except sqlite3.IntegrityError:
# 如果因为并发等原因仍冲突,跳过
pass
conn.commit()
conn.close()
return new_count
def get_due_words(self, exclude_ids=None):
"""
获取今天需要复习的单词
exclude_ids: 已在本轮复习过的ID集合
"""
if exclude_ids is None:
exclude_ids = set()
today = datetime.date.today().isoformat()
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 构建排除条件
placeholders = ','.join(['?'] * len(exclude_ids)) if exclude_ids else ''
query = f'''
SELECT id, word, meaning, example, repetitions, ease_factor, interval_days
FROM words
WHERE next_review <= ? AND id NOT IN ({placeholders})
ORDER BY random()
LIMIT 1
'''
params = [today] + list(exclude_ids) if exclude_ids else [today]
cursor.execute(query, params)
result = cursor.fetchone()
conn.close()
return result
def update_word_review(self, word_id, quality):
"""
根据用户反馈更新单词记忆参数(SM-2算法简化版)
quality: 0=忘记/错误, 1=模糊/困难, 2=正确/记得
"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 获取当前单词数据
cursor.execute('''
SELECT repetitions, ease_factor, interval_days
FROM words WHERE id = ?
''', (word_id,))
row = cursor.fetchone()
if not row:
conn.close()
return
repetitions, ease_factor, interval_days = row
today = datetime.date.today()
if quality >= 2: # 正确
if repetitions == 0:
interval_days = 1
elif repetitions == 1:
interval_days = 3
elif repetitions == 2:
interval_days = 7
elif repetitions == 3:
interval_days = 14
elif repetitions == 4:
interval_days = 30
else:
interval_days = min(interval_days * 1.5, 365) # 最长一年
repetitions += 1
# ease_factor 可适当增加,简化版暂不调整
else: # 忘记或模糊
repetitions = 0
interval_days = 1 if quality == 1 else 0 # 模糊1天,忘记当天复习
# ease_factor 降低,简化版暂不调整
# 计算下次复习日期
next_review = today + datetime.timedelta(days=interval_days)
cursor.execute('''
UPDATE words
SET repetitions = ?, ease_factor = ?, interval_days = ?,
last_review = ?, next_review = ?
WHERE id = ?
''', (repetitions, ease_factor, interval_days,
today.isoformat(), next_review.isoformat(), word_id))
conn.commit()
conn.close()
def get_today_count(self, exclude_ids=None):
"""获取今天待复习单词总数"""
if exclude_ids is None:
exclude_ids = set()
today = datetime.date.today().isoformat()
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
placeholders = ','.join(['?'] * len(exclude_ids)) if exclude_ids else ''
query = f'''
SELECT COUNT(*) FROM words
WHERE next_review <= ? AND id NOT IN ({placeholders})
'''
params = [today] + list(exclude_ids) if exclude_ids else [today]
cursor.execute(query, params)
count = cursor.fetchone()[0]
conn.close()
return count
class WordMemoryApp:
"""主应用程序类"""
def __init__(self, root):
self.root = root
self.root.title("英语单词记忆助手 - 记忆曲线版")
self.root.geometry("650x500")
self.root.resizable(False, False)
# 设置样式
self.style = ttk.Style()
self.style.configure('TLabel', font=('微软雅黑', 11))
self.style.configure('Word.TLabel', font=('微软雅黑', 28, 'bold'))
# 数据库实例
self.db = WordMemoryDB()
# 当前复习状态
self.current_word_id = None
self.current_word_data = None
self.reviewed_ids = set() # 本轮已复习的ID
# 检查并导入单词本(仅在数据库为空时自动导入默认文件)
self.md_path = "words.md"
self.check_and_import_default()
# 创建界面
self.create_widgets()
# 加载第一个单词
self.load_next_word()
def check_and_import_default(self):
"""检查默认单词本文件,如果数据库为空则自动导入"""
# 检查数据库是否已有单词
conn = sqlite3.connect(self.db.db_path)
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM words")
count = cursor.fetchone()[0]
conn.close()
if count == 0:
if os.path.exists(self.md_path):
try:
new = self.db.import_from_markdown(self.md_path)
if new > 0:
messagebox.showinfo("导入成功", f"已自动导入 {new} 个单词")
except Exception as e:
messagebox.showerror("错误", f"自动导入失败:{str(e)}")
else:
# 创建示例文件
example_content = """apple
苹果
例句:This is a red apple.
book
书
例句:I am reading a book.
computer
电脑
例句:My computer is fast.
python
蟒蛇 / Python编程语言
例句:Python is a popular programming language.
"""
with open(self.md_path, 'w', encoding='utf-8') as f:
f.write(example_content)
messagebox.showinfo("提示", f"未找到单词本,已创建示例文件:{self.md_path}\n请按格式编辑该文件后点击「重新导入单词本」导入。")
def create_widgets(self):
"""创建界面组件"""
# 顶部信息栏
top_frame = ttk.Frame(self.root, padding=10)
top_frame.pack(fill=X)
self.count_label = ttk.Label(top_frame, text="今日剩余: 计算中...")
self.count_label.pack(side=LEFT)
import_btn = ttk.Button(top_frame, text="重新导入单词本", command=self.import_markdown)
import_btn.pack(side=RIGHT)
# 中间单词显示区
mid_frame = ttk.Frame(self.root, padding=20)
mid_frame.pack(expand=True, fill=BOTH)
self.word_label = ttk.Label(mid_frame, text="", style='Word.TLabel')
self.word_label.pack(pady=20)
self.meaning_frame = ttk.Frame(mid_frame)
self.meaning_frame.pack(fill=BOTH, expand=True, pady=10)
self.meaning_label = ttk.Label(self.meaning_frame, text="",
font=('微软雅黑', 12),
wraplength=550, justify=LEFT)
self.meaning_label.pack()
self.example_label = ttk.Label(self.meaning_frame, text="",
font=('微软雅黑', 11, 'italic'),
foreground='gray',
wraplength=550, justify=LEFT)
self.example_label.pack(pady=5)
# 底部按钮区
bottom_frame = ttk.Frame(self.root, padding=10)
bottom_frame.pack(fill=X)
self.show_btn = ttk.Button(bottom_frame, text="显示释义",
command=self.show_meaning, width=15)
self.show_btn.pack(side=LEFT, padx=5)
# 评分按钮(初始隐藏)
self.rating_frame = ttk.Frame(bottom_frame)
self.rating_frame.pack(side=RIGHT)
self.forget_btn = ttk.Button(self.rating_frame, text="忘记 (0)",
command=lambda: self.rate_word(0),
state=DISABLED, width=8)
self.forget_btn.pack(side=LEFT, padx=2)
self.hard_btn = ttk.Button(self.rating_frame, text="模糊 (1)",
command=lambda: self.rate_word(1),
state=DISABLED, width=8)
self.hard_btn.pack(side=LEFT, padx=2)
self.good_btn = ttk.Button(self.rating_frame, text="记得 (2)",
command=lambda: self.rate_word(2),
state=DISABLED, width=8)
self.good_btn.pack(side=LEFT, padx=2)
def update_count_display(self):
"""更新剩余单词计数"""
count = self.db.get_today_count(self.reviewed_ids)
self.count_label.config(text=f"今日剩余复习: {count} 个单词")
def show_meaning(self):
"""显示释义和例句"""
if self.current_word_data:
_, _, meaning, example, _, _, _ = self.current_word_data
self.meaning_label.config(text=f"【释义】 {meaning}")
self.example_label.config(text=f"📖 {example}" if example else "")
# 切换按钮状态
self.show_btn.config(state=DISABLED)
self.forget_btn.config(state=NORMAL)
self.hard_btn.config(state=NORMAL)
self.good_btn.config(state=NORMAL)
def hide_meaning(self):
"""隐藏释义"""
self.meaning_label.config(text="")
self.example_label.config(text="")
self.show_btn.config(state=NORMAL)
self.forget_btn.config(state=DISABLED)
self.hard_btn.config(state=DISABLED)
self.good_btn.config(state=DISABLED)
def load_next_word(self):
"""加载下一个待复习单词"""
# 隐藏当前释义
self.hide_meaning()
# 获取下一个单词
word_data = self.db.get_due_words(self.reviewed_ids)
if word_data:
self.current_word_id = word_data[0]
self.current_word_data = word_data
word = word_data[1]
self.word_label.config(text=word)
self.reviewed_ids.add(self.current_word_id)
else:
# 没有待复习单词了
self.word_label.config(text="🎉 恭喜!今日复习完成!")
self.current_word_id = None
self.current_word_data = None
self.show_btn.config(state=DISABLED)
self.update_count_display()
def rate_word(self, quality):
"""处理用户评分"""
if self.current_word_id:
# 更新数据库
self.db.update_word_review(self.current_word_id, quality)
# 加载下一个单词
self.load_next_word()
def import_markdown(self):
"""手动导入Markdown文件(仅添加新词)"""
filename = filedialog.askopenfilename(
title="选择单词本文件",
filetypes=[("Markdown files", "*.md"), ("Text files", "*.txt"), ("All files", "*.*")]
)
if filename:
try:
new_count = self.db.import_from_markdown(filename)
# 重置复习状态(新单词立即进入待复习队列)
self.reviewed_ids.clear()
self.load_next_word()
messagebox.showinfo("成功", f"导入完成!新增 {new_count} 个单词")
except Exception as e:
messagebox.showerror("错误", f"导入失败:{str(e)}")
def main():
root = Tk()
app = WordMemoryApp(root)
root.mainloop()
if __name__ == "__main__":
main()
软件功能说明
核心特点
- 记忆曲线算法:采用简化版SM-2间隔重复算法,根据您的记忆情况自动安排复习时间
- Markdown单词本:支持从本地Markdown文件导入单词(格式示例见下方)
- 主动回忆模式:默认只显示英文单词,点击按钮后才显示释义,符合主动回忆学习法
- 进度追踪:实时显示今日剩余复习单词数量
使用方法
- 准备单词本
创建words.md文件(与脚本同一目录),格式如下:
每个词条由单词、释义、例句三行组成,词条间用空行分隔。apple 苹果 例句:This is a red apple. book 书 例句:I am reading a book. - 运行软件
首次运行会自动创建示例单词本。python word_memory.py - 学习流程
· 界面显示英文单词,释义和例句隐藏
· 点击"显示释义"按钮查看中文意思和例句
· 根据记忆情况选择:
· 忘记 (0):完全没记住,单词会很快再次出现
· 模糊 (1):有些印象但不确定,1天后复习
· 记得 (2):正确回忆,根据记忆曲线安排下次复习 - 重新导入
如需更新单词本,点击右上角"重新导入单词本"按钮选择新的MD文件。
记忆曲线算法说明
· 每次正确回答后,下次复习间隔会逐渐拉长:1天 → 3天 → 7天 → 14天 → 30天 → 最长1年
· 回答错误时,重新开始计数,并在当天或次日再次出现
· 系统每天自动计算需要复习的单词,优先显示最该复习的
打包为EXE
如需在未安装Python的电脑上运行,可使用PyInstaller打包:
pip install pyinstaller
pyinstaller --onefile --windowed --name "单词记忆助手" word_memory.py
浙公网安备 33010602011771号