资讯详情

资讯详情

python-docx实战:从题库JSON到自动化生成练习题答案Word文档

简介面向Python初学者和期末备考者的填空练习答案文档题目覆盖标准库sqrt、None空类型、type与id内置函数、列表元组字符串的有序性、切片与列表推导式、字典items与zip构造、排序与lambda、字符串join与转义字符、print分隔符等核心语法点每题均给出答案并附简短解析便于对照纠错、加深理解。压缩包内为1个docx文档大小仅165KB内容按题号逐条排列结构紧凑适合课堂随练、课后自测或考前速查。当前已有1006人学习浏览。结合预览可见题目从基础概念延伸到综合表达式判断如[1,2,3]*3的重复结果、aList[3:7]的切片区间、dict(zip(a,b))的字典构建、sorted按字符串长度排序、列表推导式筛出13的倍数等均覆盖初学者易混点既能帮助系统梳理Python语法基础也可供教师布置练习或出题参考。1. “python练习题-答案.docx”到底解决什么把出题变成数据渲染“python练习题-答案.docx”这个文件名对搞技术培训、出试卷、维护题库的开发者来说并不陌生。它可能是你第一次被叫去“帮忙排个 Word 文档”时的起点也可能是一套题目库从手工整理走向自动生成的终点。我最初接触这类文档时也以为只是把题和答案打进去结果一百多道题排下来题号错位、代码缩进消失、中文变成方框每个问题都能卡住半小时。这篇文章就围绕这类文档的生成方向讲清楚题目答案文档的结构怎么设计、用 python-docx 生成它的最小脚本长什么样、哪些参数最值得调以及真正容易翻车的地方。适合想把手动出题流程脚本化的人看新手按步骤能跑通熟手能直接拿去填自己的题库格式。2. 把 docx 拆成可生成的对象先定结构再谈脚本2.1 题目与答案的两段式结构三种排版方案决定了循环怎么写手工整理文档时大家习惯“想到哪写到哪”但在脚本里题目和答案必须成对出现否则答案错位只是时间问题。所谓成对是指每道题的数据对象里同时携带题干、选项如果是选择题、答案和解析渲染时从同一个对象取值这样题目和答案永远绑定在一起不会因为中间删了一题而错开。排版的常见方案有三种。第一种是答案紧跟题目适合随堂练习和自学阅读顺手但不利于闭卷自测第二种是题干全部在前、答案集中放在后半部分最贴近“练习册带答案”的语义也更接近考试场景第三种是每题单独一页题目和该题答案同页适合打印后讲解。三种方案对应的循环结构差别不小最好在建数据前就定下来。排版方案适用场景分页处理脚本成本答案紧跟题目随堂练习、新手自学不加分页最低题干全部在前答案集中在后考试自测、章节作业题干区结束加一次分页中等每题一页答案与题同页备课讲义、打印讲解每题结束加分页较高我一般选第二种。它最贴合“python练习题-答案.docx”这个名字而且后续想改成第一种或第三种只改主循环里的分页位置不用动数据结构。2.2 题库数据源为什么我推荐 JSON 而不是 CSV生成 docx 只是链路的一段上游的数据源才是决定维护成本的关键。如果题目全是选择题CSV 勉强够用一列题干、四列选项、一列答案表格软件里就能编辑。但一旦涉及代码题题干缩进、答案多行、解析里带引号和逗号CSV 的转义问题会立刻让数据变得不可读我甚至见过解析被逗号劈成两半导致答案错位的情况。所以我更推荐用 JSON 做题库源文件。JSON 天然支持嵌套和换行选项列表、代码字段、解析文本都可以原样存放也不会因为多一个逗号就把整行数据弄坏。一套最简的题型结构可以是这样questions [ { id: 1, type: choice, stem: 执行 print(2 ** 3 ** 2) 的结果是, options: [64, 512, Error, 72], answer: B, analysis: 幂运算的绑定规则是从右到左先算 3 ** 2 得到 9再算 2 ** 9结果是 512。 }, { id: 2, type: code, stem: 写一个函数接收整数列表并返回其中所有偶数的平方。, code: def even_squares(nums):\n return [x ** 2 for x in nums if x % 2 0], answer: 见 code 字段, analysis: 列表推导式先通过 if 过滤偶数再对每个元素做平方。 } ]id 在数据里可以重复或空缺因为渲染时脚本会按顺序重新编号type 字段用来分流choice 走选项表格code 走代码块渲染answer 对选择题来说是“B”这样的选项字母对代码题则通常指向 code 字段解析单独放 analysis。这样设计的好处是渲染逻辑只认 type 和几个固定字段后续往库里加题不需要动脚本。2.3 生成前先规划样式Normal、Heading 与代码段各管什么python-docx 的样式继承关系比很多人想象中严格。Normal 样式是所有未单独指定段落的基底所以中文字体和正文字号要先在这里定死Heading 1、Heading 2 用于标题和题目分组会直接影响目录和导航窗格代码段如果直接用 Normal 写则每个 run 都要手动设置字体、字号、加粗和底纹重复劳动很大。我一般会先建一个名为 CodeBlock 的自定义段落样式让所有代码块都挂在它下面。from docx import Document from docx.enum.style import WD_STYLE_TYPE from docx.oxml.ns import qn def ensure_code_style(doc): if CodeBlock not in [s.name for s in doc.styles]: code_style doc.styles.add_style(CodeBlock, WD_STYLE_TYPE.PARAGRAPH) code_style.base_style doc.styles[Normal] code_style.font.name Consolas code_style.font.size Pt(10) code_style._element.rPr.rFonts.set(qn(w:eastAsia), 等线) return doc.styles[CodeBlock]先判断样式是否存在是为了避免同一个进程里多次创建文档时重复 add_style 触发异常。base_style 指定为 Normal 是为了让代码段继承全局页边距和行距避免字体大小、段落间距出现突兀差异。字号 10pt 比正文略小因为代码字符密度大同样的版面能放下更多内容打印时也更省纸。3. 用 python-docx 逐题写入从空文档到输出第一个 docx3.1 最小可用脚本创建文档、设置 Normal、保存有了数据结构接下来就是最核心的部分把题库渲染成 docx。以下几段代码构成了一个可运行的最小骨架先创建一个空文档并把全局字体设好再保存成目标文件名。这个骨架不包含任何题型逻辑但它是后续所有渲染函数的基础。from docx import Document from docx.shared import Pt from docx.oxml.ns import qn def new_document(): doc Document() normal doc.styles[Normal] normal.font.name Calibri normal.font.size Pt(11) normal._element.rPr.rFonts.set(qn(w:eastAsia), 微软雅黑) return doc doc new_document() doc.save(python练习题-答案.docx)normal._element.rPr.rFonts.set(qn(w:eastAsia), 微软雅黑) 这一行是在给文档根样式补上东亚字体映射否则 Word 在中文环境里会默认套用宋体。很多“生成的文档打开后中文字体很奇怪”的问题根源都是少了这一行。Calibri 是西文字体微软雅黑是中文显示字体两项分开设置互不干扰如果你在 macOS 上生成把微软雅黑换成苹方也未尝不可但要注意目标阅读端有没有对应字体。提示save 时输出路径含中文在 Windows 和 Linux 上都正常不需要额外编码处理。3.2 选择题的渲染选项放进两列表格而不是五个段落选择题的题目区我习惯用标题和普通段落写题干选项则放进两列表格里。用连续段落也能显示但 Word 里段落之间行距大四个选项竖排会占掉半页而且不好对齐。两列表格让选项在一行里排两个版面紧凑也更像纸质练习册的排版。def add_choice(doc, q): doc.add_heading(f第{q[id]}题选择题, level3) doc.add_paragraph(q[stem]) from docx.shared import Cm rows (len(q[options]) 1) // 2 table doc.add_table(rowsrows, cols2) table.autofit False labels [A, B, C, D, E, F] for i, opt in enumerate(q[options]): row, col i // 2, i % 2 cell table.cell(row, col) cell.text f{labels[i]}. {opt} cell.width Cm(7.5) doc.add_paragraph()add_table 创建表格后autofit 置为 False 是必要动作否则 Word 会按内容自动扩列宽两个 7.5 厘米的单元格会被重新分配视觉效果不稳定。rows 用 (len 1) // 2 计算这样支持任意数量选项三个选项也能排成两行。labels 列表按顺序取选项字母多出来的选项继续用 E、F 命名测试时如果超过六个选项需要自己扩充这个列表。3.3 代码题的答案逐行写入并手动加换行代码题在 Word 里最麻烦难点不是内容而是换行和缩进必须原样保留。如果直接把包含 \n 的字符串塞进一个 runpython-docx 能保存Word 打开后却会变成一大段连续文本或乱跳行。正确做法是把代码按 \n 拆成行每一行作为一个 run 追加行与行之间用 run.add_break() 插入换行节点。def add_code_block(doc, code_text): p doc.add_paragraph() p.style ensure_code_style(doc) lines code_text.split(\n) for i, line in enumerate(lines): run p.add_run(line) if i len(lines) - 1: run.add_break() return padd_code_block 接收的是 code_text 原始字符串内部按行拆分保证每一行的缩进空格都原样进 run。add_break 插入的是文档结构里的换行节点而不是文本里的换行符这样 Word 才能正确渲染。代码字体统一由 CodeBlock 样式接管这个函数里不需要再设置字符属性改字体也只动样式定义一处。3.4 主渲染循环与输出把上面的片段串起来主循环就非常短遍历 questions按 type 分流调用对应渲染函数最后 save。这个循环里唯一要留意的是编号id 不要直接用数据的 id而是在循环里重新数。def add_answer_block(doc, q): doc.add_paragraph(f答案{q[answer]}) if q.get(analysis): doc.add_paragraph(f解析{q[analysis]}) doc new_document() for index, q in enumerate(questions, start1): q[id] index if q[type] choice: add_choice(doc, q) elif q[type] code: doc.add_heading(f第{index}题代码题, level3) doc.add_paragraph(q[stem]) add_code_block(doc, q[code]) add_answer_block(doc, q) doc.save(python练习题-答案.docx)add_choice 内部会读取 q[id] 拼题目标题所以在进入分支前必须先重写 id。把 id 改成 enumerate 的结果既能保证连续编号也避免删除中间某题后后续全乱。add_answer_block 是答案区公共函数选择题输出“答案B”代码题输出“答案见 code 字段”再统一加上解析文本。按这个写法答案与题干来自同一个数据对象不会出现两边错开。4. 让答案可读可藏可打印分页、题号与代码块底纹4.1 题目全部在前、答案集中在后的两轮渲染如果采用“题干全部在前答案集中放末尾”的方案就不能在循环里边写题目边写答案了。常见做法是遍历两遍题库第一轮只渲染题干第二轮只渲染答案区。第二轮渲染答案时要在答案段落里带上对应题号否则读者翻到后半部分根本不知道这条答案属于哪一题。def add_answer_block(doc, q, indexNone): prefix f第{index}题答案 if index else 答案 doc.add_paragraph(f{prefix}{q[answer]}) if q.get(analysis): doc.add_paragraph(f解析{q[analysis]}) # 第一轮只写题干 for i, q in enumerate(questions, start1): q[id] i if q[type] choice: add_choice(doc, q) else: doc.add_heading(f第{i}题代码题, level3) doc.add_paragraph(q[stem]) doc.add_page_break() # 第二轮只写答案与解析 for i, q in enumerate(questions, start1): add_answer_block(doc, q, i) if q[type] code and q.get(code): add_code_block(doc, q[code])这样生成的 docx 结构非常清晰前半部分是可以直接发给学员做的题目后半部分是答案。因为答案的题号来自同一轮 enumerate所以无论中间删了几题答案区依然和题干一一对应。doc.add_page_break() 会让答案区从新一页开始打印时不会和最后一题挤在同一页。4.2 头部信息与知识点标记用表格把元数据排好练习题答案 docx 除了题目还应该有课程名称、章节范围、难度、建议用时这类元数据。如果不加元数据这份文档用两个星期后连自己都分不清是哪个章节的练习。我习惯在文档开头放一个两列表格左侧放属性名右侧放属性值。meta doc.add_table(rows4, cols2) meta.style Table Grid meta_data { 课程名称: Python 基础语言练习, 章节范围: 第 3 章运算与条件, 难度: 中等, 建议用时: 60 分钟 } for i, (key, value) in enumerate(meta_data.items()): meta.cell(i, 0).text key meta.cell(i, 1).text value meta.cell(i, 0).width Cm(3) meta.cell(i, 1).width Cm(10)meta.style Table Grid 让表格带上可见边框打印时不会出现只有文字没有边框的情况。设置列宽时同样要一次性把两列都设好因为 Word 的表格列宽策略是按整列计算的只设第一列没有意义。rows4 要和 meta_data 的键数量保持一致改成 6 行 2 列就可以容纳更多元数据逻辑不变。4.3 代码块底纹、段距与控制分页有了样式定义后可以给代码段加上浅灰底纹让它和题干区分开。底纹用底层 XML 的 w:shd 元素设置python-docx 没有直接封装需要自己插入 OxmlElement。from docx.oxml import OxmlElement from docx.oxml.ns import qn def set_shading(paragraph, fillF7F7F7): pPr paragraph._p.get_or_add_pPr() shd OxmlElement(w:shd) shd.set(qn(w:val), clear) shd.set(qn(w:color), auto) shd.set(qn(w:fill), fill) pPr.append(shd)调用时把它接在 add_code_block 返回的段落对象后面code_par add_code_block(doc, q[code]) set_shading(code_par, F7F7F7)fill 参数是十六进制颜色值F7F7F7 是接近白色的浅灰打印出来不会显得脏。底纹只加在段落级如果代码块内部需要小范围强调比如把关键行标黄那要改成行级 run 的背景代价会明显变大初期不需要着急。分页方面答案在后的方案只要一次大分页如果采用“每题一页”则要在每题渲染结束后调用 doc.add_page_break()。段落间距不要全用默认建议统一设置几个关键参数否则题目之间容易挤在一起。排版参数位置建议值说明正文行距Normal.paragraph_format.line_spacing1.5适合阅读和批注题干段后距stem_par.paragraph_format.space_afterPt(6)避免题干紧贴选项代码行距CodeBlock.paragraph_format.line_spacing1.0代码行距过大浪费版面代码段上下间距CodeBlock.paragraph_format.space_before / space_afterPt(6)与题干分隔更明显5. 生成 docx 的避坑清单五类典型翻车与排查路径5.1 中文变方框或字体不对现象脚本跑得没问题docx 也能打开但中文显示成了方框、乱码或者整篇中文被强制渲染成宋体行距还被拉大。原因python-docx 的 run.font.name 只设置了西文字体属性中文字体实际由 rFonts 的 eastAsia 属性控制。不设置 eastAsia 时Word 按操作系统默认字体处理很多 Linux 和 macOS 上生成的文件到 Windows 上就会乱套。解决在每个需要中文的 run 和样式上固定写一行run._element.rPr.rFonts.set(qn(w:eastAsia), 微软雅黑)。最省事的做法是在 new_document 里把 Normal 样式一次配好后续文本全部继承不要再单独设置中文字体。5.2 代码缩进和空行在 Word 里消失现象渲染出的代码题答案里缩进全没了多行代码挤在一行或者只剩第一行。原因把一个带 \n 的完整字符串直接放进 run。python-docx 会把这个换行符当成文本内容写入 XMLWord 打开时不会按预期换行而是折叠或忽略。解决按行拆分代码后逐行 add_run并在每行末尾用 add_break() 插入换行节点。同时注意外层传给 add_code_block 的代码字符串本身必须用 \n 分隔不能是列表否则拆分逻辑要跟着变。测试时把 return 后面的缩进四个空格也打进去验证一下。5.3 表格列宽设置不生效现象cell.width 设了 7.5 厘米表格还是忽宽忽窄某一行特别宽另一行特别窄。这个坑以前在群里被当成玄学讨论。原因表格默认 autofit 为 TrueWord 会根据单元格内容重新计算每列宽度手动设置的 cell.width 会被覆盖。解决add_table 之后立刻table.autofit False然后再对每个单元格设宽度。如果还不稳定把表格布局改成 fixed也就是在 tblPr 里追加w:tblLayout w:typefixed/。前者能解决九成问题后者用于模板比较复杂、列太多的情况。5.4 题号和答案错位现象文档前面题目删了一道后面的答案区却还保留旧编号对不上号或者调整题目顺序后答案没有跟着变。原因题号是手工维护的题目列表和答案列表被当成两份独立数据在维护中间任何增删都会打破两边的同步。解决题号永远从列表顺序生成不要存进 JSON 的 id 字段也不要手工在 docx 里改。每次生成前用 enumerate 重新编号答案区的题号同样来自 enumerate。这样增删题目后重新生成一次答案自动跟着对齐。删除题目时只动 JSON 数据源不要动最终 docx。5.5 文档打开提示“需要修复”现象用同一个模板反复生成后Word 打开提示“文件中的部分内容存在问题”即使能打开样式也丢失了。原因模板文件里残留了旧样式定义、同名样式或损坏的书签python-docx 基于它新增内容时继承了脏结构。这类问题在直接拷贝他人 docx 当模板时最常出现。解决不依赖带样式模板从 Document() 空文档开始用代码把所需样式一次性声明到位。如果确实需要模板用专门制作的干净模板文件而不是拿用过的生成结果当模板。遇到文件损坏提示时把生成的 docx 用压缩软件打开检查 styles.xml 里是否有重复或冲突的样式定义清理掉再重新生成。6. 从固定题库到多套试卷随机抽题与生成后自检6.1 固定随机种子抽题生成可复现的多套试卷题库积累到几百道之后手动挑题已经不合理。每次出新卷都要手工挑不仅慢而且容易重复选到同一题学员会发现这周和上周的卷子重了好几道。可以用 random.sample 从题目列表里抽取指定数量再把抽取结果按顺序重新编号复用前面的渲染函数。import random random.seed(2026) picked random.sample(questions, k10) doc new_document() for index, q in enumerate(picked, start1): q[id] index render_one(doc, q) doc.save(python练习题-答案-随机卷A.docx)seed 固定后同一台机器同一版本 Python 跑出来的抽样结果完全一致这是可复现的试卷生成方式改 seed 就等于换一套卷。k10 表示抽取 10 道题用时更短的练习可以把 k 改小抽完后重新编号保证试卷内序号连续。6.2 生成后自检把 docx 读回来查错文档生成完并不等于正确。我的习惯是用 python-docx 把生成的 docx 再读回来检查段落数、答案字符和代码块完整性至少比肉眼逐题翻要快得多。from docx import Document def verify(path, questions): doc Document(path) text_all \n.join(p.text for p in doc.paragraphs) assert len(doc.paragraphs) 0 for q in questions: if q[type] choice: assert q[answer] in {A, B, C, D} elif q[type] code: assert q[code].count(\n) 0 return True verify(python练习题-答案.docx, questions)verify 的定位不是代替人工审题而是抓住最容易出的三类低级错误空文档、选择题答案越界、代码题缺少代码字段。assert 失败时脚本会在终端的报错信息里指出是哪一道题便于直接回数据源修改。text_all 变量在复杂校验里很有用比如统计整份文档里“答案”关键词出现次数可以和题目数做比对。我第一次做这套流程时题库还用电子表格维护答案和题干靠复制粘贴对齐结果新增二十道题后整体错位重排到凌晨。后来狠心把题库全部改造成 JSON 驱动脚本生成 docx同样的更新半小时内完成。从那以后我养成一个习惯凡是 Word 文档只要能拆成结构化数据就绝不手排。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →