当前位置:首页>python>Python小学数学四则运算题库生成工具 V3.0

Python小学数学四则运算题库生成工具 V3.0

  • 2026-09-05 20:55:05
Python小学数学四则运算题库生成工具 V3.0

📐 小学数学四则运算题库生成工具 V3.0 — 让出题像呼吸一样简单

一款基于 Python Tkinter 打造的桌面级数学题库生成器,支持加减乘除混合运算、多数值多位数自由组合、括号嵌套、竖式计算、脱式计算等多种题型,可自定义难度参数并一键导出 Word/竖式/脱式格式文档,是小学数学教师备课和家长辅导的高效利器。


✨ 开场白

数学是思维的体操,而四则运算则是这场体操的基本功。对于小学数学教师而言,每天面对的一个现实问题就是:如何快速、高效地生成大量难度适中、格式规范的练习题?手工出题不仅耗时耗力,还容易出现重复或难度失控的问题;而市面上的在线出题工具往往功能单一,无法满足"竖式计算""脱式计算""括号运算"等多样化教学需求。

小学数学四则运算题库生成工具 V3.0 正是为解决这一痛点而设计的专业桌面应用。它完全基于 Python 标准库 Tkinter 构建,无需网络、无需付费、一个文件即可运行,却提供了堪比专业教辅软件的功能深度。

从参数维度来看,本工具支持 2~5 个数值参与运算,每个数值可独立设置位数(一位数到四位数、随机、小数、分数),运算符支持加减乘除任意组合,答案范围从 10 到 9999 可选,还能为连续数值添加括号形成复合运算。高级选项方面,支持允许负数结果、小数答案、除法余数等灵活配置,以及竖式计算(限2个数)和脱式计算(最少3个数)两种专业题型模式。

操作流程上,工具采用"生成→预览→暂存→导出"的工作流:点击生成即可在右侧预览区实时查看题目效果,满意后暂存到题库,最终一键导出为 Word 文档(需 python-docx)、竖式格式或脱式格式的文本文件,直接打印即可用于课堂练习或家庭作业。

整个项目约 500 行精炼代码,涵盖了随机数生成、表达式求值、括号分组算法、多格式导出等核心技术,既是实用的教学工具,也是学习 Python GUI 开发和算法设计的优秀实战项目。


📋 功能特性一览

模块
功能
数值系统
2~5个数值,每个独立设置位数(1~4位/随机/小数/分数)
运算符
加/减/乘/除 任意多选组合
括号系统
勾选连续数值自动添加括号
答案控制
10~9999 范围可选,支持负数/小数/余数
竖式模式
限2个数,标准竖式排版
脱式模式
最少3个数,逐步计算格式
暂存系统
多次生成累积暂存,统一导出
导出格式
Word文档 / 竖式文本 / 脱式文本
实时预览
右侧即时显示题目与答案

🏗️ 第一部分:架构设计与核心算法

整体架构

程序以 MathQuizApp 单类封装,按职责划分为四大模块:

  • UI 层:_build_left_panel(参数面板)/ _build_right_panel(预览区)
  • 生成引擎:_gen_number / _generate_one / _generate
  • 表达式处理:_evaluate / _format_expression / _find_bracket_groups
  • 导出系统:_export_word / _export_vertical / _export_strip

数值生成策略

根据位数类型生成对应范围的随机数,支持整数、小数和分数:

def_gen_number(self, digit_type, max_val=9999):if digit_type == "一位数":return random.randint(1, 9)elif digit_type == "二位数":return random.randint(10, 99)elif digit_type == "三位数":return random.randint(100, 999)elif digit_type == "小数(2位)":return round(random.uniform(0.01, 99.99), 2)elif digit_type == "分数":        denom = random.choice([2, 3, 4, 5, 6, 8, 10])        numer = random.randint(1, denom - 1)return Fraction(numer, denom)

括号分组算法

通过扫描连续勾选的标志位,自动识别需要添加括号的数值组:

def_find_bracket_groups(self, bracket_flags, num_count):    groups = []    i = 0while i < num_count:if i < len(bracket_flags) and bracket_flags[i]:            start = iwhile i < num_count and i < len(bracket_flags) and bracket_flags[i]:                i += 1if i - start >= 2:  # 至少2个连续才成组                groups.append((start, i - 1))else:            i += 1return groups

例如勾选数值1、2、3 → 生成 (a + b + c);勾选1、2、4、5 → 生成 (a + b) ○ c ○ (d + e)。


🎯 第二部分:题目生成引擎

单题生成流程

_generate_one 方法是核心,采用"生成-验证-重试"策略:

  1. 根据参数生成 N 个随机数值
  2. 随机选择 N-1 个运算符
  3. 处理除法整除约束(调整被除数)
  4. 计算表达式结果
  5. 验证答案范围、正负性、小数性
  6. 格式化输出表达式和答案
def_generate_one(self):for _ in range(200):  # 最多尝试200次        numbers = [self._gen_number(digit_type) for ...]        operators = [random.choice(ops) for ...]# 除法整除处理for i, op in enumerate(operators):if op == '÷'andnot allow_dec:                numbers[i] = quotient * numbers[i+1]        result = self._evaluate(numbers, operators)# 验证约束if abs(result) > answer_range: continueifnot allow_neg and result < 0: continuereturn {"expr": expr, "answer": answer, ...}

表达式求值

利用 Python 的 eval() 进行安全计算(输入完全由程序生成,无注入风险):

def_evaluate(self, numbers, operators):    expr = str(numbers[0])for i, op in enumerate(operators):if op == '×': expr += f"*{numbers[i+1]}"elif op == '÷': expr += f"/{numbers[i+1]}"else: expr += f"{op}{numbers[i+1]}"return eval(expr)

🖨️ 第三部分:多格式显示与导出

竖式计算显示

标准竖式排版,对齐数位,预留答案书写区:

  1.        45    +   38    ──────

脱式计算显示

逐步展开格式,留出多行计算过程:

  1. 25 + 38 × 4 - 12     = ________________     = ________________     = ________________

Word 导出

优先使用 python-docx 库生成标准 Word 文档,包含标题、题目和答案页;若未安装则自动降级为纯文本导出:

try:from docx import Document    doc = Document()    doc.add_heading("四则运算练习题", level=1)for i, q in enumerate(questions):        p = doc.add_paragraph()        p.add_run(f"{i+1}. {q['expr']} = ______").font.size = Pt(14)    doc.save(path)except ImportError:# 降级为txt导出    self._export_as_txt(questions, path)

💻 完整源代码

"""小学数学四则运算题库生成工具 V3.0功能:随机生成四则运算练习题,支持自定义题目难度与格式,可导出Word/竖式/脱式题目"""import tkinter as tkfrom tkinter import ttk, filedialog, messageboximport randomimport mathfrom fractions import Fractionimport os============ 常量 ============VERSION = "3.0"DIGIT_OPTIONS = ["一位数", "二位数", "三位数", "四位数", "随机", "小数(2位)", "分数"]ANSWER_RANGE_OPTIONS = [10, 20, 50, 100, 200, 300, 500, 999, 5000, 9999]OP_SYMBOLS = {'+': '+', '-': '-', '×': '×', '÷': '÷'}class MathQuizApp:def init(self, root):self.root = rootself.root.title(f"小学数学四则运算题库生成工具 V{VERSION}")self.root.geometry("1350x820")self.root.configure(bg="#f5f5f5")self.root.resizable(True, True)    # 暂存题目列表    self.stored_questions = []    # 当前预览题目    self.current_questions = []    self._build_ui()def _build_ui(self):    # 顶部标题栏    header = tk.Frame(self.root, bg="#2196F3", height=55)    header.pack(fill=tk.X)    header.pack_propagate(False)    tk.Label(header, text="📐 小学数学四则运算题库生成工具 V3.0",             font=("SimHei", 15, "bold"), bg="#2196F3", fg="white"             ).pack(side=tk.LEFT, padx=20, pady=12)    # 主体    body = tk.Frame(self.root, bg="#f5f5f5")    body.pack(fill=tk.BOTH, expand=True, padx=10, pady=8)    # 左侧参数面板    self._build_left_panel(body)    # 右侧预览面板    self._build_right_panel(body)def _build_left_panel(self, parent):    left = tk.Frame(parent, bg="white", width=520, relief=tk.RIDGE, bd=1)    left.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 8))    left.pack_propagate(False)    # 滚动区域    canvas = tk.Canvas(left, bg="white", highlightthickness=0)    sb = ttk.Scrollbar(left, orient="vertical", command=canvas.yview)    sf = tk.Frame(canvas, bg="white")    sf.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))    canvas.create_window((0, 0), window=sf, anchor="nw", width=500)    canvas.configure(yscrollcommand=sb.set)    sb.pack(side=tk.RIGHT, fill=tk.Y)    canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)    pad = {"padx": 12, "pady": 4}    # === 数值个数 ===    tk.Label(sf, text="🔢 数值个数", font=("SimHei", 10, "bold"), bg="white"             ).pack(anchor="w", **pad)    self.num_count_var = tk.IntVar(value=5)    nc_frame = tk.Frame(sf, bg="white")    nc_frame.pack(anchor="w", **pad)    for n in [2, 3, 4, 5]:        tk.Radiobutton(nc_frame, text=str(n), variable=self.num_count_var, value=n,                      bg="white", font=("SimSun", 10)).pack(side=tk.LEFT, padx=8)    # === 数值位数设置 ===    tk.Label(sf, text="📏 数值位数设置", font=("SimHei", 10, "bold"), bg="white"             ).pack(anchor="w", **pad)    self.digit_vars = []    for i in range(5):        frame = tk.Frame(sf, bg="white")        frame.pack(fill=tk.X, **pad)        tk.Label(frame, text=f"数值{i+1}:", bg="white", font=("SimSun", 10),                 width=6, anchor="w").pack(side=tk.LEFT)        var = tk.StringVar(value="二位数")        combo = ttk.Combobox(frame, textvariable=var, values=DIGIT_OPTIONS,                            width=12, state="readonly", font=("SimSun", 9))        combo.pack(side=tk.LEFT, padx=4)        self.digit_vars.append(var)    # === 括号设置 ===    tk.Label(sf, text="🔗 括号设置(勾选连续数值添加括号)", font=("SimHei", 10, "bold"),             bg="white").pack(anchor="w", **pad)    self.bracket_vars = []    br_frame = tk.Frame(sf, bg="white")    br_frame.pack(anchor="w", **pad)    for i in range(5):        var = tk.BooleanVar(value=False)        tk.Checkbutton(br_frame, text=f"数值{i+1}", variable=var, bg="white",                      font=("SimSun", 9)).pack(side=tk.LEFT, padx=4)        self.bracket_vars.append(var)    # === 答案范围 ===    tk.Label(sf, text="🎯 答案范围", font=("SimHei", 10, "bold"), bg="white"             ).pack(anchor="w", **pad)    self.answer_range_var = tk.IntVar(value=9999)    ar_frame = tk.Frame(sf, bg="white")    ar_frame.pack(anchor="w", **pad)    for i, val in enumerate(ANSWER_RANGE_OPTIONS):        tk.Radiobutton(ar_frame, text=str(val), variable=self.answer_range_var,                      value=val, bg="white", font=("SimSun", 9)                      ).grid(row=i // 5, column=i % 5, padx=4, pady=2)    # === 题目数量 ===    tk.Label(sf, text="📝 题目数量", font=("SimHei", 10, "bold"), bg="white"             ).pack(anchor="w", **pad)    qn_frame = tk.Frame(sf, bg="white")    qn_frame.pack(anchor="w", **pad)    self.question_count_spin = ttk.Spinbox(qn_frame, from_=1, to=200, width=8,                                            font=("SimSun", 11))    self.question_count_spin.set(20)    self.question_count_spin.pack(side=tk.LEFT)    tk.Label(qn_frame, text=" 道", bg="white", font=("SimSun", 10)).pack(side=tk.LEFT)    # === 运算方法 ===    tk.Label(sf, text="➕ 运算方法(可多选)", font=("SimHei", 10, "bold"), bg="white"             ).pack(anchor="w", **pad)    self.op_vars = {}    op_frame = tk.Frame(sf, bg="white")    op_frame.pack(anchor="w", **pad)    for op, label in [('+', '加法'), ('-', '减法'), ('×', '乘法'), ('÷', '除法')]:        var = tk.BooleanVar(value=True)        tk.Checkbutton(op_frame, text=label, variable=var, bg="white",                      font=("SimSun", 10)).pack(side=tk.LEFT, padx=8)        self.op_vars[op] = var    # === 高级选项 ===    tk.Label(sf, text="⚙️ 高级选项", font=("SimHei", 10, "bold"), bg="white"             ).pack(anchor="w", **pad)    self.allow_negative = tk.BooleanVar(value=False)    self.allow_decimal_answer = tk.BooleanVar(value=False)    self.allow_remainder = tk.BooleanVar(value=False)    self.vertical_calc = tk.BooleanVar(value=False)    self.strip_calc = tk.BooleanVar(value=False)    adv_options = [        (self.allow_negative, "允许结果为负数"),        (self.allow_decimal_answer, "答案允许带小数"),        (self.allow_remainder, "除法有余数(限2个数)"),        (self.vertical_calc, "竖式计算(限2个数)"),        (self.strip_calc, "脱式计算(最少3个数)"),    ]    for var, text in adv_options:        tk.Checkbutton(sf, text=text, variable=var, bg="white",                      font=("SimSun", 10)).pack(anchor="w", **pad)    # === 操作按钮 ===    tk.Label(sf, text="", bg="white").pack(pady=5)    btn_frame = tk.Frame(sf, bg="white")    btn_frame.pack(fill=tk.X, padx=12, pady=5)    tk.Button(btn_frame, text="🎲 生成题目", command=self._generate,             font=("SimHei", 11), bg="#2196F3", fg="white", relief=tk.FLAT,             padx=12, pady=5).pack(side=tk.LEFT, padx=4)    tk.Button(btn_frame, text="📥 暂存题目", command=self._store,             font=("SimHei", 11), bg="#4CAF50", fg="white", relief=tk.FLAT,             padx=12, pady=5).pack(side=tk.LEFT, padx=4)    btn_frame2 = tk.Frame(sf, bg="white")    btn_frame2.pack(fill=tk.X, padx=12, pady=5)    tk.Button(btn_frame2, text="🗑 清空暂存", command=self._clear_store,             font=("SimHei", 10), bg="#ff9800", fg="white", relief=tk.FLAT,             padx=8, pady=4).pack(side=tk.LEFT, padx=3)    tk.Button(btn_frame2, text="📄 导出Word", command=self._export_word,             font=("SimHei", 10), bg="#9C27B0", fg="white", relief=tk.FLAT,             padx=8, pady=4).pack(side=tk.LEFT, padx=3)    tk.Button(btn_frame2, text="📐 导出竖式", command=self._export_vertical,             font=("SimHei", 10), bg="#607D8B", fg="white", relief=tk.FLAT,             padx=8, pady=4).pack(side=tk.LEFT, padx=3)    tk.Button(btn_frame2, text="📝 导出脱式", command=self._export_strip,             font=("SimHei", 10), bg="#795548", fg="white", relief=tk.FLAT,             padx=8, pady=4).pack(side=tk.LEFT, padx=3)    # 暂存计数    self.store_label = tk.Label(sf, text="暂存:0 道题", bg="white",                                fg="#666666", font=("SimSun", 10))    self.store_label.pack(anchor="w", padx=12, pady=5)def _build_right_panel(self, parent):    right = tk.Frame(parent, bg="white", relief=tk.RIDGE, bd=1)    right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)    # 顶部栏    top = tk.Frame(right, bg="#f8f9fa", height=40)    top.pack(fill=tk.X)    top.pack_propagate(False)    tk.Label(top, text="📋 题目预览", font=("SimHei", 10, "bold"),             bg="#f8f9fa").pack(side=tk.LEFT, padx=10, pady=8)    tk.Button(top, text="清空显示", command=self._clear_preview,             font=("SimSun", 9), relief=tk.FLAT, bg="#e0e0e0"             ).pack(side=tk.RIGHT, padx=10, pady=6)    # 文本预览区    text_frame = tk.Frame(right, bg="white")    text_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)    self.preview_text = tk.Text(text_frame, font=("Consolas", 12), wrap=tk.WORD,                                bg="#fafafa", fg="#333333", relief=tk.SOLID, bd=1,                                spacing1=4, spacing3=4)    scroll = ttk.Scrollbar(text_frame, command=self.preview_text.yview)    self.preview_text.configure(yscrollcommand=scroll.set)    scroll.pack(side=tk.RIGHT, fill=tk.Y)    self.preview_text.pack(fill=tk.BOTH, expand=True)def _clear_preview(self):    self.preview_text.delete("1.0", tk.END)    self.current_questions = []# ============ 数值生成 ============def _gen_number(self, digit_type, max_val=9999):    """根据位数类型生成数值"""    if digit_type == "一位数":        return random.randint(1, 9)    elif digit_type == "二位数":        return random.randint(10, 99)    elif digit_type == "三位数":        return random.randint(100, 999)    elif digit_type == "四位数":        return random.randint(1000, 9999)    elif digit_type == "随机":        return random.randint(1, min(max_val, 999))    elif digit_type == "小数(2位)":        return round(random.uniform(0.01, min(max_val, 99.99)), 2)    elif digit_type == "分数":        denom = random.choice([2, 3, 4, 5, 6, 8, 10])        numer = random.randint(1, denom - 1)        return Fraction(numer, denom)    return random.randint(1, 99)def _get_ops(self):    """获取选中的运算符"""    ops = []    if self.op_vars['+'].get():        ops.append('+')    if self.op_vars['-'].get():        ops.append('-')    if self.op_vars['×'].get():        ops.append('×')    if self.op_vars['÷'].get():        ops.append('÷')    if not ops:        ops = ['+']    return opsdef _evaluate(self, numbers, operators):    """计算表达式结果(按运算符优先级)"""    try:        expr = str(numbers[0])        for i, op in enumerate(operators):            n = numbers[i + 1]            if op == '+':                expr += f"+{n}"            elif op == '-':                expr += f"-{n}"            elif op == '×':                expr += f"*{n}"            elif op == '÷':                if n == 0:                    return None                expr += f"/{n}"        result = eval(expr)        return result    except:        return Nonedef _format_number(self, n):    """格式化数值显示"""    if isinstance(n, Fraction):        return str(n)    elif isinstance(n, float):        if n == int(n):            return str(int(n))        return str(round(n, 2))    return str(n)def _format_expression(self, numbers, operators, brackets=None):    """格式化表达式字符串"""    parts = []    for i, n in enumerate(numbers):        parts.append(self._format_number(n))    # 应用括号    if brackets:        # 找连续勾选的组        groups = self._find_bracket_groups(brackets, len(numbers))        # 构建表达式        expr = ""        i = 0        while i < len(numbers):            in_group = False            for start, end in groups:                if i == start:                    expr += "("                    for j in range(start, end + 1):                        expr += parts[j]                        if j < end:                            expr += f" {operators[j]} "                    expr += ")"                    if end < len(numbers) - 1:                        expr += f" {operators[end]} "                    i = end + 1                    in_group = True                    break            if not in_group:                expr += parts[i]                if i < len(numbers) - 1:                    expr += f" {operators[i]} "                i += 1        return expr    else:        expr = parts[0]        for i, op in enumerate(operators):            expr += f" {op}{parts[i+1]}"        return exprdef _find_bracket_groups(self, bracket_flags, num_count):    """找出连续勾选的括号组"""    groups = []    i = 0    while i < num_count:        if i < len(bracket_flags) and bracket_flags[i]:            start = i            while i < num_count and i < len(bracket_flags) and bracket_flags[i]:                i += 1            if i - start >= 2:                groups.append((start, i - 1))        else:            i += 1    return groups# ============ 题目生成 ============def _generate_one(self):    """生成一道题"""    num_count = self.num_count_var.get()    answer_range = self.answer_range_var.get()    ops = self._get_ops()    allow_neg = self.allow_negative.get()    allow_dec = self.allow_decimal_answer.get()    allow_rem = self.allow_remainder.get()    # 获取括号设置    brackets = [self.bracket_vars[i].get() for i in range(num_count)]    max_attempts = 200    for _ in range(max_attempts):        # 生成数值        numbers = []        for i in range(num_count):            digit_type = self.digit_vars[i].get() if i < len(self.digit_vars) else "二位数"            numbers.append(self._gen_number(digit_type, answer_range))        # 生成运算符        operators = [random.choice(ops) for _ in range(num_count - 1)]        # 处理除法:确保能整除(除非允许余数/小数)        for i, op in enumerate(operators):            if op == '÷' and numbers[i + 1] != 0:                if not allow_dec and not allow_rem:                    # 调整被除数使其能整除                    divisor = numbers[i + 1] if not isinstance(numbers[i + 1], Fraction) else 1                    if divisor != 0 and isinstance(divisor, int):                        quotient = random.randint(1, max(1, answer_range // max(1, abs(divisor))))                        numbers[i] = quotient * divisor            elif op == '÷' and numbers[i + 1] == 0:                numbers[i + 1] = random.randint(1, 9)        # 计算结果        result = self._evaluate(numbers, operators)        if result is None:            continue        # 检查答案范围        if isinstance(result, (int, float)):            if abs(result) > answer_range:                continue            if not allow_neg and result < 0:                continue            if not allow_dec and isinstance(result, float) and result != int(result):                continue        # 格式化表达式        expr = self._format_expression(numbers, operators, brackets)        answer = self._format_number(result) if not isinstance(result, Fraction) else str(result)        if isinstance(result, float) and result == int(result):            answer = str(int(result))        return {"expr": expr, "answer": answer, "numbers": numbers, "operators": operators}    # 兜底:简单加法    a, b = random.randint(1, 50), random.randint(1, 50)    return {"expr": f"{a} + {b}", "answer": str(a + b), "numbers": [a, b], "operators": ['+']}def _generate(self):    """生成题目并显示"""    count = int(self.question_count_spin.get())    self.current_questions = []    # 特殊模式检查    if self.vertical_calc.get():        self.num_count_var.set(2)    if self.strip_calc.get() and self.num_count_var.get() < 3:        self.num_count_var.set(3)    for i in range(count):        q = self._generate_one()        self.current_questions.append(q)    # 显示到预览区    self.preview_text.delete("1.0", tk.END)    self.preview_text.insert(tk.END, f"{'='*50}\n")    self.preview_text.insert(tk.END, f"  四则运算练习题(共 {count} 道)\n")    self.preview_text.insert(tk.END, f"{'='*50}\n\n")    for i, q in enumerate(self.current_questions):        if self.vertical_calc.get():            self._display_vertical(i + 1, q)        elif self.strip_calc.get():            self._display_strip(i + 1, q)        else:            line = f"  {i+1:>3}. {q['expr']} = ______\n"            self.preview_text.insert(tk.END, line)    self.preview_text.insert(tk.END, f"\n{'─'*50}\n")    self.preview_text.insert(tk.END, f"  参考答案:\n")    for i, q in enumerate(self.current_questions):        self.preview_text.insert(tk.END, f"  {i+1}. {q['answer']}  ")        if (i + 1) % 5 == 0:            self.preview_text.insert(tk.END, "\n")    self.preview_text.insert(tk.END, "\n")def _display_vertical(self, idx, q):    """竖式显示"""    nums = q['numbers']    op = q['operators'][0] if q['operators'] else '+'    a = self._format_number(nums[0])    b = self._format_number(nums[1]) if len(nums) > 1 else "0"    width = max(len(a), len(b)) + 2    self.preview_text.insert(tk.END, f"  {idx}.\n")    self.preview_text.insert(tk.END, f"      {a:>{width}}\n")    self.preview_text.insert(tk.END, f"    {op}{b:>{width}}\n")    self.preview_text.insert(tk.END, f"    {'─' * (width + 2)}\n")    self.preview_text.insert(tk.END, f"      {'':>{width}}\n\n")def _display_strip(self, idx, q):    """脱式显示"""    self.preview_text.insert(tk.END, f"  {idx}. {q['expr']}\n")    self.preview_text.insert(tk.END, f"     = ________________\n")    self.preview_text.insert(tk.END, f"     = ________________\n")    self.preview_text.insert(tk.END, f"     = ________________\n\n")# ============ 暂存管理 ============def _store(self):    """暂存当前题目"""    if not self.current_questions:        messagebox.showinfo("提示", "请先生成题目")        return    self.stored_questions.extend(self.current_questions)    self.store_label.config(text=f"暂存:{len(self.stored_questions)} 道题")    messagebox.showinfo("暂存成功", f"已暂存 {len(self.current_questions)} 道题\n"                       f"累计暂存:{len(self.stored_questions)} 道")def _clear_store(self):    """清空暂存"""    if self.stored_questions:        if messagebox.askyesno("确认", f"确定清空 {len(self.stored_questions)} 道暂存题目?"):            self.stored_questions = []            self.store_label.config(text="暂存:0 道题")    else:        messagebox.showinfo("提示", "暂存区为空")# ============ 导出功能 ============def _get_export_questions(self):    """获取要导出的题目(优先暂存,否则当前)"""    if self.stored_questions:        return self.stored_questions    elif self.current_questions:        return self.current_questions    else:        messagebox.showinfo("提示", "没有可导出的题目,请先生成或暂存题目")        return Nonedef _export_word(self):    """导出为Word文档(纯文本docx格式)"""    questions = self._get_export_questions()    if not questions:        return    path = filedialog.asksaveasfilename(defaultextension=".docx",                                        filetypes=[("Word文档", "*.docx"), ("文本文件", "*.txt")])    if not path:        return    try:        # 尝试使用python-docx        try:            from docx import Document            from docx.shared import Pt, Cm            from docx.enum.text import WD_ALIGN_PARAGRAPH            doc = Document()            # 标题            title = doc.add_heading("四则运算练习题", level=1)            title.alignment = WD_ALIGN_PARAGRAPH.CENTER            doc.add_paragraph(f"共 {len(questions)} 道题")            # 题目            for i, q in enumerate(questions):                p = doc.add_paragraph()                p.paragraph_format.space_after = Pt(8)                p.add_run(f"{i+1}. {q['expr']} = ______").font.size = Pt(14)            # 答案页            doc.add_page_break()            doc.add_heading("参考答案", level=2)            answer_text = ""            for i, q in enumerate(questions):                answer_text += f"{i+1}.{q['answer']}  "                if (i + 1) % 8 == 0:                    answer_text += "\n"            doc.add_paragraph(answer_text)            doc.save(path)            messagebox.showinfo("导出成功", f"Word文档已保存到:\n{path}")        except ImportError:            # 降级为txt            if not path.endswith('.txt'):                path = path.replace('.docx', '.txt')            self._export_as_txt(questions, path)    except Exception as e:        messagebox.showerror("导出失败", str(e))def _export_vertical(self):    """导出竖式计算"""    questions = self._get_export_questions()    if not questions:        return    path = filedialog.asksaveasfilename(defaultextension=".txt",                                        filetypes=[("文本文件", "*.txt")])    if not path:        return    try:        lines = []        lines.append("竖式计算练习题")        lines.append("=" * 40)        lines.append("")        for i, q in enumerate(questions):            nums = q['numbers']            op = q['operators'][0] if q['operators'] else '+'            a = self._format_number(nums[0])            b = self._format_number(nums[1]) if len(nums) > 1 else "0"            width = max(len(a), len(b)) + 2            lines.append(f"{i+1}.")            lines.append(f"    {a:>{width}}")            lines.append(f"  {op}{b:>{width}}")            lines.append(f"  {'─' * (width + 2)}")            lines.append(f"    {'':>{width}}")            lines.append("")        lines.append("─" * 40)        lines.append("参考答案:")        for i, q in enumerate(questions):            lines.append(f"  {i+1}. {q['answer']}")        with open(path, "w", encoding="utf-8") as f:            f.write("\n".join(lines))        messagebox.showinfo("导出成功", f"竖式题目已保存到:\n{path}")    except Exception as e:        messagebox.showerror("导出失败", str(e))def _export_strip(self):    """导出脱式计算"""    questions = self._get_export_questions()    if not questions:        return    path = filedialog.asksaveasfilename(defaultextension=".txt",                                        filetypes=[("文本文件", "*.txt")])    if not path:        return    try:        lines = []        lines.append("脱式计算练习题")        lines.append("=" * 40)        lines.append("")        for i, q in enumerate(questions):            lines.append(f"{i+1}. {q['expr']}")            lines.append(f"   = ________________")            lines.append(f"   = ________________")            lines.append(f"   = ________________")            lines.append("")        lines.append("─" * 40)        lines.append("参考答案:")        for i, q in enumerate(questions):            lines.append(f"  {i+1}. {q['answer']}")        with open(path, "w", encoding="utf-8") as f:            f.write("\n".join(lines))        messagebox.showinfo("导出成功", f"脱式题目已保存到:\n{path}")    except Exception as e:        messagebox.showerror("导出失败", str(e))def _export_as_txt(self, questions, path):    """降级导出为纯文本"""    lines = []    lines.append("四则运算练习题")    lines.append(f"共 {len(questions)} 道题")    lines.append("=" * 40)    lines.append("")    for i, q in enumerate(questions):        lines.append(f"{i+1}. {q['expr']} = ______")    lines.append("")    lines.append("─" * 40)    lines.append("参考答案:")    for i, q in enumerate(questions):        lines.append(f"  {i+1}. {q['answer']}")    with open(path, "w", encoding="utf-8") as f:        f.write("\n".join(lines))    messagebox.showinfo("导出成功", f"文本文件已保存到:\n{path}\n(未安装python-docx,已降级为txt)")============ 程序入口 ============if name == "main":root = tk.Tk()app = MathQuizApp(root)root.mainloop()

最新文章

随机文章