Python随机出题器:从约束设计到PDF试卷生成

发布时间:2026/10/16 17:43:20

Python随机出题器:从约束设计到PDF试卷生成 简介这是一份用Python编写的随机出题小工具面向想给小朋友练习加减乘除的家长或编程初学者通过运行脚本即可自动生成口算题目省去手动出题的麻烦。压缩包共352个文件以288个py源码文件为主另含25个pyd、11个exe、6个dll等运行依赖以及少量txt、xml、cfg、bat配置与说明文件整体约8.32MB属于可直接运行的完整项目环境。目前已有9049人学习下载热度较高。资源亮点在于出题逻辑清晰适合边玩边学作者还特别提示了Python中true除法与floor除法同数学除法的差异遇到除不尽的情况可引导孩子用分数表示或口述答案并借此讲解编程与数学的区别。读者可借此理解随机数生成、运算符选择、循环与输入判断等基础用法也能直接拿来当亲子练习工具或在此基础上改造成闯关、计时、错题记录等玩法。1. 随机出题器为什么你写的random.randint总被家长一眼看穿用 Python 实现随机出题加减乘除听起来像是编程入门第一课的水平但我见过太多版本在真实场景里活不过三分钟。某位家长给孩子用了一个网上抄来的出题脚本第一天孩子做了 50 道题第二天发现题目和昨天一模一样第三天直接不做了——因为脚本每次启动都用固定种子或者更常见的题目池子太小两位数加减法翻来覆去就那几十种组合。随机出题的核心矛盾从来不是「能不能随机」而是「随机得有没有教学价值」除法不能出不尽减法不能出负数乘法不能超纲加减乘除混合时运算优先级不能让孩子崩溃。这篇文章面向的是想给自己孩子、学生或培训班做一套可持续使用的口算练习工具的开发者我会从出题引擎的约束设计讲到批量生成 PDF 的完整链路中间穿插我踩过的血泪坑比如浮点数精度导致除法判错、题目去重时集合把字典吃掉这类翻车现场。读完你能拿到一套可直接跑的代码也能明白每个参数为什么这么设。2. 出题引擎的约束设计让随机数服从教学逻辑2.1 四则运算各自的合法域与边界条件纯随机生成a b很容易但教学场景下每个运算符都有隐含约束。加法通常要求结果不超过某个上限比如 100 以内减法要求被减数大于等于减数乘法要求两个因数都在乘法表范围内中文语境下一般是 1~9 或 1~12除法要求被除数能被除数整除且除数不为零。这些约束如果不在生成阶段处理就得在渲染阶段过滤而过滤会导致题目数量不可控——你设了 50 道实际输出可能只有 32 道。我一般把每个运算符的约束写成一个独立的校验函数生成时循环重试直到满足条件。重试次数要设上限否则遇到极端参数比如要求 100 以内减法且结果必须大于 90会死循环。下面是一个可复用的约束校验模块import random def validate_add(a, b, max_result100): 加法结果不超过上限且两个加数都为正整数 return a 0 and b 0 and (a b) max_result def validate_sub(a, b, allow_negativeFalse): 减法默认不允许负数结果 if allow_negative: return a 0 and b 0 return a b and b 0 def validate_mul(a, b, max_factor9): 乘法因数在乘法表范围内 return 1 a max_factor and 1 b max_factor def validate_div(a, b, max_quotient9): 除法整除、除数非零、商在范围内 if b 0: return False return a % b 0 and 1 (a // b) max_quotient这段代码的关键在于把「合法性」和「生成」分离。validate_div里先判b 0再取模顺序不能反否则 ZeroDivisionError 直接让脚本崩掉。max_quotient控制商的上限避免出现 81 ÷ 1 81 这种虽然整除但超纲的题。参数max_factor默认 9 对应中文乘法表如果做英文数学练习可以调到 12。2.2 用重试洗牌替代纯随机避免题目扎堆直接random.randint生成 50 道题大概率会出现连续多道3 5这种重复。更隐蔽的问题是分布不均加法题里结果小于 10 的会占很大比例因为小数字组合多。解决办法是先生成一个候选池再从中采样。我的做法是对每个运算符枚举所有合法组合存入列表然后用random.sample抽取。这样既保证不重复又保证分布均匀。候选池的大小要提前估算——100 以内加法大约有 5000 种组合乘法表只有 81 种除法商≤9大约 200 种。如果要求的题目数量超过池子大小就得允许重复或放宽约束。def build_pool(op, count, **kwargs): 构建候选池并采样count 为需要的题目数 pool [] if op : for a in range(1, 100): for b in range(1, 100): if validate_add(a, b, kwargs.get(max_result, 100)): pool.append((a, , b)) elif op -: for a in range(1, 100): for b in range(1, a 1): if validate_sub(a, b): pool.append((a, -, b)) elif op *: mf kwargs.get(max_factor, 9) for a in range(1, mf 1): for b in range(1, mf 1): pool.append((a, *, b)) elif op /: mq kwargs.get(max_quotient, 9) for b in range(1, mq 1): for q in range(1, mq 1): pool.append((b * q, /, b)) if len(pool) count: # 池子不够就允许重复采样 return random.choices(pool, kcount) return random.sample(pool, count)random.choices和random.sample的区别要记牢前者允许重复后者不允许。当池子不够时用choices兜底避免程序报错。build_pool里除法的构造方式比较绕——先枚举除数和商再乘出被除数这样保证整除。如果反过来枚举被除数和除数再判断整除循环次数会多很多。2.3 混合运算的优先级处理与括号策略当题目从单一运算符升级到加减乘除混合时运算优先级就成了必须处理的问题。3 5 * 2和(3 5) * 2结果不同如果随机拼接运算符而不加括号孩子算出来的答案可能和程序预期不一致。常见做法有两种一是只生成同级运算全是加减或全是乘除二是生成混合运算但强制加括号明确优先级。我倾向于对小学低年级只出同级运算高年级再引入混合。如果要做混合用eval验证答案时要注意安全问题——eval会执行任意代码虽然这里输入可控但养成习惯用ast.literal_eval或自己写解析器更好。实际项目中我直接用一个简单的栈式求值函数避免引入eval这个黑匣子。def safe_eval(expr): 仅支持 - * / 和括号的极简求值避免 eval 的安全隐患 # 将表达式转为 Python 可求值形式后限制命名空间 allowed {__builtins__: None} try: result eval(expr, allowed, {}) return result except Exception: return None把__builtins__设为None能挡住大部分危险调用但这不是绝对安全的方案。如果题目表达式完全由你自己的代码生成风险可控如果允许用户输入表达式必须换用ast模块做语法树校验。参数allowed里还可以加入abs、round等白名单函数但四则运算用不上。3. 从题目列表到可打印试卷排版、去重与答案分离3.1 用 reportlab 生成 PDF 试卷的最小可用配置出题引擎跑通后下一步是把题目列表变成能打印的试卷。纯文本输出太简陋家长需要的是带题号、留白、分栏的 PDF。Python 生态里生成 PDF 的库不少reportlab是最成熟的选择之一安装命令是pip install reportlab。下面是一个生成 A4 试卷的最小示例from reportlab.lib.pagesizes import A4 from reportlab.pdfgen import canvas from reportlab.lib.units import mm def generate_pdf(questions, filenameworksheet.pdf, cols2): questions 为 (a, op, b) 列表cols 控制分栏数 c canvas.Canvas(filename, pagesizeA4) width, height A4 margin 20 * mm col_width (width - 2 * margin) / cols line_height 12 * mm y height - margin col 0 for idx, (a, op, b) in enumerate(questions, 1): x margin col * col_width c.drawString(x, y, f{idx}. {a} {op} {b} ______) y - line_height if y margin: col 1 y height - margin if col cols: c.showPage() col 0 y height - margin c.save()A4的尺寸是 595×842 点mm是毫米转点的系数。margin设 20mm 是打印安全边距低于 10mm 可能被打印机裁掉。line_height控制行距12mm 大约能写下一行算式加答案。分栏逻辑里col和y的复位容易写错——每次换页要把col归零、y重置到顶部否则第二页会从中间开始。drawString的坐标原点在左下角所以y是递减的。3.2 题目去重为什么 set 会吃掉你的题目顺序去重听起来简单list(set(questions))一行搞定但 set 是无序的去重后题目顺序全乱而且如果题目用字典表示比如{a: 3, op: , b: 5}字典不可哈希set 直接报 TypeError。这是新手最常见的翻车点之一。正确做法是用有序去重遍历列表用辅助 set 记录已见过的题目保留首次出现的顺序。def dedupe_ordered(questions): 保持顺序的去重questions 为元组列表 seen set() result [] for q in questions: if q not in seen: seen.add(q) result.append(q) return result如果题目是字典先转成元组(a, op, b)再去重输出时再转回字典。seen集合的查找是 O(1)整体复杂度 O(n)比双重循环快得多。注意元组里如果包含列表比如选项列表同样不可哈希需要转成字符串或冻结集合。3.3 答案页分离与题号对齐的细节试卷和答案必须分开打印否则孩子直接抄答案。答案页的题号要和试卷严格对齐否则家长批改时找不到对应题。我的做法是生成题目时同时保留原始索引答案页按相同顺序输出。def generate_answer_pdf(questions, filenameanswers.pdf): 答案页每行显示题号和答案 c canvas.Canvas(filename, pagesizeA4) width, height A4 y height - 20 * mm for idx, (a, op, b) in enumerate(questions, 1): ans eval(f{a} {op} {b}) c.drawString(20 * mm, y, f{idx}. {a} {op} {b} {ans}) y - 8 * mm if y 20 * mm: c.showPage() y height - 20 * mm c.save()答案页的行距可以比试卷小8mm 足够。eval在这里只用于计算已知合法的表达式风险可控但如果你介意可以换成operator模块的add、sub、mul、truediv映射。题号从 1 开始递增和试卷的enumerate(questions, 1)保持一致。如果试卷分栏后题号是横向排列的答案页也要用相同的排列逻辑否则家长对不上号。4. 避坑与排查那些让出题器翻车的细节4.1 除法结果浮点数精度导致判错现象6 / 3在 Python 里结果是2.0和整数2比较时2.0 2为 True但如果你用is比较或者把结果转成字符串再比对就会出问题。更隐蔽的是0.1 0.2 ! 0.3这类浮点误差在生成小数运算题时会让答案校验失败。原因IEEE 754 浮点数的二进制表示无法精确表达某些十进制小数。解决除法题强制用整数运算判断整除用a % b 0商用a // b。如果必须出小数题用decimal.Decimal或fractions.Fraction比较时设一个容差abs(x - y) 1e-9。4.2 随机种子固定导致每次题目相同现象脚本每次运行生成的题目完全一样孩子做第二遍时直接背答案。原因代码里写了random.seed(42)或者某个固定值或者用了random.Random(0)实例。解决去掉固定种子或者用当前时间戳random.seed()不传参。如果做「同一套题重复打印」的功能把种子存下来下次用同一种子复现但默认模式必须随机。4.3 题目数量与池子大小不匹配导致死循环现象设置生成 200 道乘法题程序卡住不动。原因乘法表 1~9 只有 81 种组合random.sample要求样本数不超过池子大小超过就抛 ValueError如果用 while 循环重试就变成死循环。解决生成前检查len(pool) count不够就放宽约束比如乘法因数扩到 12或允许重复。用random.choices替代random.sample可以避免报错但要去重的话得另想办法。4.4 PDF 中文乱码与字体缺失现象试卷里的中文标题显示成方块或问号。原因reportlab默认字体不支持中文需要注册中文字体。解决用pdfmetrics.registerFont(TTFont(SimSun, simsun.ttf))注册系统字体然后c.setFont(SimSun, 12)。字体文件路径要写对Linux 服务器上可能没有中文字体需要提前安装或打包字体文件。如果只是数字和符号用默认的 Helvetica 就够了不必折腾字体。4.5 运算符优先级导致答案与预期不符现象混合运算题2 3 * 4程序算出来是 14但你想让孩子先算加法得到 20。原因Python 的eval遵循标准优先级乘法先于加法。解决如果要强制从左到右计算必须加括号(2 3) * 4或者自己写一个从左到右的求值器。出题时明确标注「先乘除后加减」或「从左到右」不要让孩子猜规则。5. 进阶技巧用模板与配置文件驱动批量出题走到这里你已经有一套能跑的出题器了。但如果每次换题型都要改代码维护成本会很高。我的习惯是把题目配置抽成 JSON 或 YAML用模板驱动生成。比如下面这个配置定义了三套试卷{ worksheets: [ { title: 一年级口算·20以内加法, ops: [], count: 50, max_result: 20, cols: 2 }, { title: 二年级口算·乘法表, ops: [*], count: 60, max_factor: 9, cols: 3 }, { title: 三年级混合·加减乘除, ops: [, -, *, /], count: 40, max_result: 100, max_factor: 9, max_quotient: 9, cols: 2 } ] }读取配置后循环调用前面的build_pool和generate_pdf就能批量产出不同难度的试卷。ops列表里如果包含多个运算符需要按比例分配题目数量比如加减乘除各 10 道或者按权重随机抽取。我一般用random.choices(ops, weights...)控制各运算符的出现频率避免除法题过多让孩子崩溃。验证生成结果是否合理我常用两个指标一是题目重复率用len(set(questions)) / len(questions)计算低于 0.9 就说明池子太小或采样有问题二是答案分布把答案画成直方图如果某个数值占比超过 20%说明分布偏斜需要调整约束。这两个检查用collections.Counter几行就能搞定。最后一个血泪教训不要在孩子面前调试出题器。我曾经当着孩子的面改代码结果生成了一套除法题全是81 ÷ 9孩子做了五遍同样的题当场识破。后来我养成的习惯是先在本地跑一遍用assert检查题目数量和重复率确认无误再打印。出题器的价值不在于代码多优雅而在于孩子拿到手的那张纸是不是真的在帮他练习而不是在帮他背答案。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/16 17:43:20

C#控制Nikon相机:SDK架构与自动化拍摄实战指南

简介:这是一套面向C#开发者的Nikon相机SDK资源包,借助桌面软件即可让电脑远程控制尼康相机,支持视频录制、连拍、单拍以及图片优化等核心功能,适合需要二次开发相机控制程序、批量拍摄或自动化工作流的开发者,也适合摄…

2026/10/16 17:38:19

KV Cache量化工程账:从FP16到INT4能省多少显存

做推理优化的朋友应该都有这种体验:模型参数明明已经压得很小了,结果上下文一拉长、batch 一大,显存曲线照样涨得让人心慌。先别急着怪框架,拿 profiler 扫一眼,大概率是 KV Cache 在里头默默膨胀。KV Cache 量化就是我…

2026/10/16 17:38:19

银行支付接口对接实战:报文签名、异步回调与对账全解析

前阵子我们电商平台接到一个需求:接入一家商业银行的支付产品,用来解决大额B2B交易的线上支付和分账问题。我们对接的是这家银行的聚付通支付接口,整个过程从看文档到联调通过,前前后后花了将近两个月,中间踩的坑多到能…

2026/10/16 20:08:49

强化学习入门:环境与智能体解耦,轻松替换算法

简介:这份资源面向强化学习初学者与需要快速上手实践的开发者,提供一套可直接运行的乒乓球对战模拟实例,帮助理解智能体如何通过与环境交互不断优化击球策略。压缩包共10个文件,以6个pth模型权重、3个py脚本和1个pyc缓存文件为主&…

2026/10/16 20:08:49

法人股东号码筛选系统:从号码核验到精准拓客的实战指南

做B2B这么多年,我最大的感触是:销售拼到最后,往往不是话术,而是名单。一份质量过硬的名单,能把电销团队的接通率从20%拉到50%以上;一份鱼龙混杂的“脏名单”,不仅浪费坐席时间,还可能…

2026/10/16 20:08:49

PaddleOCR微调导出模型权重加载不生效的解决指南

简介:PaddleOCR 2.6 版本实操教程以一份 docx 文档形式分享,面向需要快速入门 OCR 环境搭建与模型训练的开发者。文档系统梳理了六大关键环节:虚拟环境配置、PPOCRLabel 标签生成、训练/测试数据集划分、YML 配置文件调整、模型训练与验证&am…

2026/10/16 20:08:49

卷积神经网络入门:从卷积核到特征图,彻底搞懂卷积算法

1. 卷积到底在卷什么:从一次图像分类翻车说起很多人第一次接触卷积神经网络,都是从一行Conv2d(in_channels3, out_channels64, kernel_size3)开始的。代码跑通了,模型训练了,准确率也上去了,但要是问一句“这个卷积核到…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/16 0:01:11

Cephadm实战指南:Linux系统工程师的生产级Ceph集群部署与排障

简介:本资源是一份完整的Ceph分布式存储系统中文技术文档合集,面向Linux系统运维工程师、云平台架构师及存储方向开发者,帮助读者系统掌握Ceph集群的部署、配置与运维核心能力。文档覆盖对象存储(RGW)、块设备&#xf…

2026/10/16 0:01:11

ITK中SpatialObject几何体系解析:坐标系、变换与管道集成

1. 空间对象的定位:为什么 ITK 需要一套几何描述体系很多刚开始接触 ITK 的朋友,包括我自己,最初都是被它的图像滤波、配准、分割这些“正统”功能吸引进来的。等真正上手做点实际项目,比如把分割出来的器官边界做量化分析、给血管…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑