1. 项目概述为什么一个处理 Word 的 Python 包值得单独写一篇万字实操笔记“python docx包”——这五个字在日常办公自动化、教育材料批量生成、合同模板填充、考试题库导出等场景里出现频率高得惊人。但凡你用 Python 做过哪怕一次“把 Excel 里的学生名单塞进 Word 通知单”或者“从数据库读出 200 份客户信息自动生成带编号的 Word 报告”你就一定会撞上python-docx这个包。它不是最炫的没有 AI 生成能力不支持 PDF 转换也不做 OCR但它像一把磨得极锋利的瑞士军刀——轻、稳、准、不挑环境装完就能用改三行代码就能跑通整个流程。我最早接触它是在某高校教务系统的辅助工具开发中需要把每学期 37 门课的《教学日历》按统一格式导出为 Word每份含封面、课程信息表、周次教学安排三大部分且必须保留学校红头文件样式、页眉页脚编号、表格边框线粗细要求 0.5 磅、中文宋体小四、英文 Times New Roman。当时试过用 win32com 调用本地 Word 进程——结果在服务器上直接报错“找不到 COM 对象”也试过用 docxtpl 模板引擎但发现它底层仍依赖 python-docx且模板语法一旦嵌套三层以上就容易漏字段最后回归 python-docx 原生 API配合手动构建Document对象 精确控制Paragraph样式 表格单元格合并逻辑两周内交付了稳定运行三年无故障的批量生成模块。这不是一个“学了就能吹牛”的炫技工具而是一个真实生产环境中被反复验证过的、低耦合、高可控、易调试的文档操作基石。它的核心价值不在“能做什么”而在“不会意外做什么”不弹窗、不依赖 Office 安装、不触发宏安全警告、不修改原始 .docx 的隐藏元数据、不因系统语言切换导致字体崩坏。这些细节只有在连续处理 5000 份 Word、跨 Windows/Linux/macOS 三端部署、对接 Jenkins 自动化流水线后才会真正刻进肌肉记忆。本文不讲“安装 pip install python-docx 就完事”也不堆砌 API 文档翻译。我会带你从零开始用一个真实可复现的“学生成绩分析报告生成器”项目贯穿全文——它要自动读取 CSV 成绩表计算平均分/最高分/不及格率插入带样式的标题、分节符、三线表、柱状图占位框含 Alt 文本说明最后保存为符合教务处格式规范的 .docx 文件。过程中每一个document.add_paragraph()调用背后我都告诉你为什么这里不能用runs[0].bold True而必须新建paragraph.style为什么表格列宽设为Inches(2.5)比Cm(6.35)更可靠为什么.add_heading(一、总体分析, level1)后必须手动设置大纲级别才能被 Word 导航窗格识别……这些不是“最佳实践”而是踩过坑、测过 17 种边界情况后唯一能保证输出稳定的写法。适合谁读如果你是刚学完 Python 基础、正卡在“怎么把数据变成人能看懂的文档”这一步的新手如果你是用过几次 python-docx 但总被样式错乱、段落丢失、表格变形折磨的老手如果你正在评估是否该引入 docxtpl 或 reportlab 替代方案——那么这篇笔记就是为你写的。它不假设你懂 XML 结构但会带你看到.docx文件解压后_rels/.rels和word/document.xml里到底发生了什么它不要求你背 API但会让你理解Paragraph和Run的父子关系为何决定了加粗和颜色必须分两步设置它不承诺“十分钟学会”但保证你读完后能独立写出一份带目录、页眉、水印、多级列表的合规 Word 报告。2. 内容整体设计与思路拆解为什么不用模板引擎为什么坚持原生 API2.1 项目目标倒推技术选型从“教务处验收标准”反向定义需求我们先明确这个项目的硬性出口标准——这是所有技术决策的起点。某高校教务处对《学期成绩分析报告》有明文格式要求封面校徽图片PNG宽 120pt居中、报告标题黑体二号加粗段前 36pt段后 24pt、生成日期楷体小四右对齐距下边界 2cm正文一级标题“一、总体分析”黑体小三左对齐段前 24pt段后 18pt二级标题“1.1 平均分统计”仿宋_GB2312 四号加粗左对齐段前 12pt段后 6pt表格三线表顶线、底线为 1.5 磅栏目线为 0.75 磅表头居中加粗数据右对齐数字保留 1 位小数页眉左侧“XX大学教务处”右侧“第 X 页 共 Y 页”宋体五号距边界 1.5cm页脚无内容但需预留 2.5cm 页脚区域用于后续盖章其他全文禁止使用自动编号列表因 Word 版本兼容问题易错乱所有编号需手动插入图片必须带 Alt 文本供视障人士阅读不得包含任何宏或 ActiveX 控件这些要求看似琐碎实则划出了清晰的技术红线不能依赖本地 Word 应用→ 排除 win32com、pywin32不能破坏原有样式继承链→ 排除直接修改document.styles[Normal]这类全局操作必须精确控制磅值与物理单位→ 要求 API 支持Pt,Inches,Cm等单位转换且单位解析稳定需动态计算页数→ 要求能获取当前 section 的页码范围而非静态写死此时再看主流方案方案是否满足封面图片精确定位是否可控页眉页脚距离是否支持三线表线宽独立设置是否能绕过 Word 自动编号陷阱部署复杂度python-docx 原生✅InlineShape.width Inches(1.67)120pt÷72✅section.header_distance Cm(1.5)✅table.rows[0].cells[0].vertical_alignment WD_CELL_VERTICAL_ALIGNMENT.CENTER 单独设边框✅ 手动插入§符号文本模拟编号极低纯 Pythondocxtpl 模板引擎⚠️ 图片尺寸受模板单元格约束易拉伸⚠️ 页眉需在模板中预设无法动态计算页数⚠️ 边框样式需在模板中固化无法按数据条件切换✅ 模板内写死编号中需维护 .docx 模板文件reportlab docx2python❌ 生成 PDF 再转 docx 会丢失所有样式精度❌ 页眉页脚需二次注入 XML风险高❌ 表格边框为矢量渲染转 docx 后线宽失真❌ 转换过程破坏段落结构高多库协同版本冲突多结论很清晰当输出格式存在强行政约束时原生 API 是唯一能实现像素级控制的路径。模板引擎本质是“用 Word 设计界面用 Python 填数据”而原生 API 是“用 Python 重写 Word 的排版指令”。前者快后者稳——本项目要的是“稳”。2.2 架构设计三层分离拒绝面条代码我将整个生成流程拆为三个逻辑层每层职责单一便于调试和复用数据层Data Layer负责从 CSV/Excel/数据库读取原始数据清洗如处理空值、类型转换计算衍生指标平均分、标准差、不及格率。关键点绝不在此层做任何 Word 相关操作只输出标准 Python 数据结构dict/list。样式层Style Layer定义所有可复用的样式对象。不是简单调document.add_heading()而是预先创建Style实例并注册到document.styles中。例如# 创建“一级标题”样式基于 Heading 1 但修改字体和间距 title_style document.styles.add_style(ReportTitle, WD_STYLE_TYPE.PARAGRAPH) title_style.base_style document.styles[Heading 1] title_style.font.name SimHei title_style.font.size Pt(22) title_style.paragraph_format.space_before Pt(36) title_style.paragraph_format.space_after Pt(24)这样后续所有一级标题都用paragraph.style document.styles[ReportTitle]确保全文字体、间距、缩进完全一致。文档层Document Layer纯粹执行“把数据按样式规则塞进文档骨架”的动作。它只调用document.add_paragraph(),table.add_row(),section.footer.is_linked_to_previous False等基础方法不包含任何业务逻辑或计算。这一层代码应像流水线工人一样机械、可预测。这种分层不是为了炫技而是为了解决一个实际痛点某次教务处临时要求“将不及格率阈值从 60 分下调至 55 分”如果业务逻辑和文档生成混在一起你得翻遍 300 行代码找if score 60而分层后只需改数据层里一行fail_threshold 55其他层完全不动。2.3 关键决策背后的原理为什么放弃“自动样式继承”python-docx 文档对象有一个styles属性里面预置了Normal,Heading 1等内置样式。新手常犯的错误是直接修改它们# ❌ 危险操作会污染全局样式 document.styles[Normal].font.name SimSun这会导致后续所有未指定样式的段落都变成宋体但更隐蔽的问题是Word 的样式继承机制在 .docx 解析时存在缓存行为。当你用document.add_paragraph(测试文本)时python-docx 并非实时读取styles[Normal]的当前状态而是依据初始化时的快照。我在某次升级 python-docx 到 0.8.11 后发现同样代码在 Windows 上正常在 Linux 上字体全变 Times New Roman——根源就是styles[Normal]的修改未被新版本的 XML 序列化逻辑捕获。正确做法是永远创建新样式永不修改内置样式。即使只是微调也要走add_style()流程# ✅ 安全操作创建新样式显式应用 body_style document.styles.add_style(BodyText, WD_STYLE_TYPE.PARAGRAPH) body_style.base_style document.styles[Normal] # 继承基础属性 body_style.font.name SimSun body_style.font.size Pt(12) # 后续所有正文段落都显式指定 p document.add_paragraph(这里是正文, styleBodyText)这样做的代价是代码略长但收益巨大样式变更可预测、跨平台一致、调试时能清晰看到每个段落绑定的样式名用p.style.name打印即可且避免了不同 python-docx 版本间样式解析的差异。3. 核心细节解析与实操要点从段落到表格那些文档工程师才知道的坑3.1 段落Paragraph的真相它不是“文字容器”而是“格式指令集合”很多教程把Paragraph描述成“一段文字”这是严重误导。实际上Paragraph是 Word Open XML 中w:p元素的 Python 封装它本身不存储文字只存储段落级格式指令缩进、对齐、间距、编号、制表位而文字内容由其子对象Run承载。一个Paragraph可以包含多个Run每个Run可以有独立的字体、颜色、加粗状态。这就解释了为什么这段代码无效p document.add_paragraph(重要提示请按时提交) p.bold True # ❌ AttributeError: Paragraph object has no attribute bold因为bold是Run的属性不是Paragraph的。正确写法是p document.add_paragraph() p.add_run(重要提示).bold True p.add_run(请按时提交) # 不加粗的普通文本更关键的是段落对齐方式必须在创建时指定不能事后修改# ❌ 无效align 无法赋值 p document.add_paragraph(右对齐文本) p.alignment WD_PARAGRAPH_ALIGNMENT.RIGHT # 运行时报错 # ✅ 正确通过 paragraph_format 设置 p document.add_paragraph(右对齐文本) p.paragraph_format.alignment WD_PARAGRAPH_ALIGNMENT.RIGHT为什么这样设计因为 Word 的 XML 结构中段落对齐是w:pPrw:jc w:valright//w:pPr属于段落属性块pPr而p.alignment这种简写接口在 python-docx 中并未暴露。paragraph_format才是真正的底层访问入口。提示paragraph_format是ParagraphFormat类实例它包含所有段落级格式属性。常用操作必须记牢space_before/space_after段前/段后间距单位Ptline_spacing行距1.0单倍2.0双倍first_line_indent首行缩进负值表示悬挂缩进left_indent/right_indent左右缩进tab_stops.add_tab_stop(Inches(1.0))添加制表位用于对齐数字列3.2 Run 对象字体控制的唯一合法入口Run是文字格式的最小单位。一个Paragraph可以有多个Run每个Run可以有独立字体、大小、颜色、加粗、斜体、下划线、删除线、上/下标。这是实现“同一段内部分文字加粗、部分变色、部分上标”的唯一方式。但要注意Run的字体设置是“覆盖式”的不是“叠加式”的。例如run p.add_run(Python) run.font.name Times New Roman run.font.size Pt(14) run.bold True # 此时 run.font.name 已被设为 Times New Roman后续再设 run.font.name SimSun 会覆盖前值所以如果你需要同时设置中英文字体中文用宋体英文用 Times New Roman必须分开两个Runp document.add_paragraph() p.add_run(Python ).font.name Times New Roman # 英文 Run p.add_run(编程入门).font.name SimSun # 中文 Run更隐蔽的坑是Run.font.name设置的是“西文字体”中文显示效果取决于系统字体回退机制。在无中文字体的 Linux 服务器上font.name SimSun可能失效。解决方案是同时设置font.cs_nameComplex Script 字体run p.add_run(中文文本) run.font.name Times New Roman # 西文字体影响英文字母 run.font.cs_name SimSun # 复杂文字字体影响中文 run._element.rPr.rFonts.set(qn(w:eastAsia), SimSun) # 强制 XML 层指定东亚字体最后一行是直接操作底层 XML 元素qn()是lxml.etree.QName的简写用于生成带命名空间的标签。这是 python-docx 官方文档未提及但在跨平台部署时救命的技巧。3.3 表格Table三线表实现的完整链路“三线表”是学术和行政文档的刚需但 python-docx 默认创建的表格是全边框。要实现顶线 1.5 磅、底线 1.5 磅、栏目线 0.75 磅、内部横线无边框需分三步操作第一步创建表格并清除默认边框table document.add_table(rows1, cols4, styleTable Grid) # 清除所有默认边框 for row in table.rows: for cell in row.cells: for border in [top, left, bottom, right, insideH, insideV]: if hasattr(cell._tc.tcPr, tcBorders): getattr(cell._tc.tcPr.tcBorders, border, None) # 更稳妥的方式直接清空 tcBorders if cell._tc.tcPr.tcBorders is not None: cell._tc.tcPr.tcBorders.clear()第二步设置顶线和底线# 顶线第一行所有单元格的 top 边框 for cell in table.rows[0].cells: set_cell_border(cell, top{sz: 18, val: single, color: 000000}) # 18 1.5pt * 12 # 底线最后一行所有单元格的 bottom 边框 last_row table.rows[-1] for cell in last_row.cells: set_cell_border(cell, bottom{sz: 18, val: single, color: 000000})第三步设置栏目线第二行的 top 边框# 栏目线第二行索引为1所有单元格的 top 边框0.75 磅 9 if len(table.rows) 1: header_row table.rows[1] for cell in header_row.cells: set_cell_border(cell, top{sz: 9, val: single, color: 000000})其中set_cell_border是自定义函数封装了 XML 操作def set_cell_border(cell, **borders): Set cells border borders: dict with keys: top, bottom, left, right, insideH, insideV each value: dict with keys: sz (border size), val (border type), color (hex) tc cell._tc tcPr tc.get_or_add_tcPr() tcBorders tcPr.get_or_add_tcBorders() for border_name, border_spec in borders.items(): border_elm OxmlElement(fw:{border_name}) border_elm.set(qn(w:val), border_spec.get(val, single)) border_elm.set(qn(w:sz), str(border_spec.get(sz, 12))) border_elm.set(qn(w:color), border_spec.get(color, 000000)) tcBorders.append(border_elm)注意sz值不是磅值而是“半磅单位”half-points。1 磅 12 半磅所以 1.5 磅 180.75 磅 9。这是 Open XML 规范python-docx 未做单位转换封装必须手动计算。3.4 页眉页脚为什么section.header.is_linked_to_previous False是必写项Word 文档可分多节Section每节可有独立页眉页脚。python-docx 创建的文档默认只有一个节但当你插入分节符如“下一页”后新节的页眉页脚默认链接到前一节。这意味着如果你在第一节页眉写了“XX大学”第二节页眉也会同步显示“XX大学”无法单独设置“附录 A”。因此每次创建新节后必须立即断开链接# 插入分节符 document.add_page_break() # 获取新节最后一个节 new_section document.sections[-1] # 断开页眉页脚链接 new_section.header.is_linked_to_previous False new_section.footer.is_linked_to_previous False # 然后才能向新页眉添加内容 header new_section.header header_para header.paragraphs[0] if header.paragraphs else header.add_paragraph() header_para.text 附录 A详细数据表 header_para.alignment WD_PARAGRAPH_ALIGNMENT.CENTER漏掉is_linked_to_previous False是新人最高频的坑。现象是改了第二节页眉第一节页眉也跟着变或者第二节页眉死活加不上内容。根源就是链接未断开所有操作都作用在第一节的页眉对象上。4. 实操过程与核心环节实现从零开始构建“学生成绩分析报告生成器”4.1 环境准备与依赖确认我们使用 python-docx 0.8.11当前最新稳定版它兼容 Python 3.7无需额外依赖。安装命令pip install python-docx0.8.11注意不要安装docx旧版已废弃或docx2python功能定位不同。验证安装import docx print(docx.__version__) # 应输出 0.8.11项目结构建议grade_report/ ├── data/ │ └── scores.csv # 原始成绩数据 ├── templates/ │ └── logo.png # 校徽图片 ├── src/ │ ├── data_loader.py # 数据层 │ ├── style_manager.py # 样式层 │ └── report_builder.py # 文档层 └── generate_report.py # 主入口4.2 数据层实现清洗、计算、结构化输出data_loader.py负责读取 CSV 并返回结构化数据。我们假设scores.csv格式如下student_id,name,math,english,physics 2023001,张三,85,92,78 2023002,李四,90,88,85 2023003,王五,55,62,48核心代码import csv from pathlib import Path from typing import List, Dict, Any def load_scores(csv_path: str) - List[Dict[str, Any]]: 加载成绩数据返回字典列表 scores [] with open(csv_path, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: # 类型转换与空值处理 score_dict { student_id: row[student_id], name: row[name], math: float(row[math]) if row[math] else 0.0, english: float(row[english]) if row[english] else 0.0, physics: float(row[physics]) if row[physics] else 0.0, } scores.append(score_dict) return scores def calculate_stats(scores: List[Dict]) - Dict[str, Any]: 计算统计指标 if not scores: return {} subjects [math, english, physics] stats {total_students: len(scores)} for subj in subjects: values [s[subj] for s in scores] stats[f{subj}_avg] round(sum(values) / len(values), 1) stats[f{subj}_max] max(values) stats[f{subj}_fail_rate] round( sum(1 for v in values if v 60) / len(values) * 100, 1 ) # 计算总分排名按 mathenglishphysics for s in scores: s[total_score] s[math] s[english] s[physics] scores.sort(keylambda x: x[total_score], reverseTrue) for i, s in enumerate(scores): s[rank] i 1 return stats # 示例调用 if __name__ __main__: data load_scores(data/scores.csv) stats calculate_stats(data) print(stats) # 输出{total_students: 3, math_avg: 76.7, math_max: 90.0, math_fail_rate: 33.3, ...}关键点编码处理明确指定encodingutf-8避免 Windows 记事本保存的 CSV 出现乱码。空值防御float(row[math]) if row[math] else 0.0防止 CSV 中有空字符串导致float()报错。统计逻辑隔离calculate_stats只返回 dict不碰任何 Word 对象为后续替换数据源如从数据库读取留出接口。4.3 样式层实现构建可复用的样式体系style_manager.py预定义所有样式。重点在于样式名必须语义化且与 Word 内置样式名区分开。from docx import Document from docx.enum.style import WD_STYLE_TYPE from docx.shared import Pt, Inches, Cm from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from docx.oxml.ns import qn from docx.oxml import OxmlElement def setup_styles(document: Document): 为文档设置全套样式 # 1. 封面标题样式 cover_title document.styles.add_style(CoverTitle, WD_STYLE_TYPE.PARAGRAPH) cover_title.base_style document.styles[Title] cover_title.font.name SimHei cover_title.font.size Pt(28) cover_title.font.color.rgb RGBColor(0, 0, 0) cover_title.paragraph_format.alignment WD_PARAGRAPH_ALIGNMENT.CENTER cover_title.paragraph_format.space_before Pt(48) cover_title.paragraph_format.space_after Pt(24) # 2. 一级标题样式黑体小三 heading1 document.styles.add_style(Heading1, WD_STYLE_TYPE.PARAGRAPH) heading1.base_style document.styles[Heading 1] heading1.font.name SimHei heading1.font.size Pt(16) heading1.paragraph_format.space_before Pt(24) heading1.paragraph_format.space_after Pt(18) heading1.paragraph_format.left_indent Inches(0.0) # 3. 二级标题样式仿宋四号 heading2 document.styles.add_style(Heading2, WD_STYLE_TYPE.PARAGRAPH) heading2.base_style document.styles[Heading 2] heading2.font.name FangSong_GB2312 heading2.font.size Pt(14) heading2.font.bold True heading2.paragraph_format.space_before Pt(12) heading2.paragraph_format.space_after Pt(6) heading2.paragraph_format.left_indent Inches(0.0) # 4. 正文样式宋体小四 body document.styles.add_style(BodyText, WD_STYLE_TYPE.PARAGRAPH) body.base_style document.styles[Normal] body.font.name SimSun body.font.size Pt(12) body.paragraph_format.line_spacing 1.25 body.paragraph_format.first_line_indent Inches(0.3) # 5. 表格标题样式居中加粗 table_title document.styles.add_style(TableTitle, WD_STYLE_TYPE.PARAGRAPH) table_title.base_style document.styles[Normal] table_title.font.name SimSun table_title.font.size Pt(12) table_title.font.bold True table_title.paragraph_format.alignment WD_PARAGRAPH_ALIGNMENT.CENTER table_title.paragraph_format.space_after Pt(6) # 6. 表格数据样式右对齐数字 table_data document.styles.add_style(TableData, WD_STYLE_TYPE.PARAGRAPH) table_data.base_style document.styles[Normal] table_data.font.name SimSun table_data.font.size Pt(12) table_data.paragraph_format.alignment WD_PARAGRAPH_ALIGNMENT.RIGHT table_data.paragraph_format.space_after Pt(0) return document # 使用示例 # doc Document() # doc setup_styles(doc)实操心得样式名如CoverTitle、Heading1必须与 Word 内置样式名Title,Heading 1不同否则add_style()会失败。base_style指定继承来源确保基础格式如行距、缩进不丢失。4.4 文档层实现组装报告的完整流水线report_builder.py是核心。我们按报告结构分块实现4.4.1 封面页生成from docx.shared import Inches, Pt, RGBColor from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from docx.oxml.ns import qn from docx.oxml import OxmlElement def add_cover_page(document: Document, stats: Dict, logo_path: str None): 添加封面页 # 清空默认段落封面页通常无正文段落 for _ in range(len(document.paragraphs)): p document.paragraphs[0] p._element.getparent().remove(p._element) # 插入校徽居中 if logo_path and Path(logo_path).exists(): try: # 计算 120pt 宽度120 ÷ 72 1.666... 英寸 logo_para document.add_paragraph() logo_para.alignment WD_PARAGRAPH_ALIGNMENT.CENTER run logo_para.add_run() run.add_picture(logo_path, widthInches(1.67)) except Exception as e: print(f封面图片加载失败: {e}) # 添加主标题 title_para document.add_paragraph(学期成绩分析报告, styleCoverTitle) # 添加副标题生成日期 from datetime import datetime date_para document.add_paragraph( f生成日期{datetime.now().strftime(%Y年%m月%d日)}, styleBodyText ) date_para.alignment WD_PARAGRAPH_ALIGNMENT.RIGHT date_para.paragraph_format.space_before Pt(36) # 添加页眉封面页通常无页眉但需预留空间 section document.sections[0] section.top_margin Cm(3.0) # 封面页上边距加大 section.bottom_margin Cm(2.5) section.left_margin Cm(2.5) section.right_margin Cm(2.5) # 强制分页到下一页 document.add_page_break()4.4.2 正文页生成总体分析章节def add_overall_analysis(document: Document, stats: Dict): 添加总体分析章节 # 一级标题 h1 document.add_paragraph(一、总体分析, styleHeading1) # 二级标题 h2 document.add_paragraph(1.1 基本情况, styleHeading2) # 正文段落 p1 document.add_paragraph(styleBodyText) p1.add_run(本学期共).bold True p1.add_run(f {stats[total_students]} ).bold False p1.add_run(名学生参与考核各科目平均分、最高分及不及格率统计如下) # 插入三线表 table document.add_table(rows1, cols4, styleTable Grid) hdr_cells table.rows[0].cells hdr_cells[0].text 科目 hdr_cells[1].text 平均分 hdr_cells[2].text 最高分 hdr_cells[3].text 不及格率 # 添加数据行 subjects [math, english, physics] subject_names [数学, 英语, 物理] for i, subj in enumerate(subjects): row_cells table.add_row().cells row_cells[0].text subject_names[i] row_cells[1].text str(stats[f{subj}_avg]) row_cells[2].text str(stats[f{subj}_max]) row_cells[3].text f{stats[f{subj}_fail_rate]}% # 应用三线表样式 apply_three_line_table(table) # 添加说明段落 p2 document.add_paragraph(styleBodyText) p2.add_run(注).bold True p2.add_run( 不及格率 不及格人数 / 总人数 × 100%不及格标准为 60 分以下。) def apply_three_line_table(table): 应用三线表样式 # 清除所有边框 for row in table.rows: for cell in row.cells: if cell._tc.tcPr.tcBorders is not None: cell._tc.tcPr.tcBorders.clear() # 顶线第一行 for cell in table.rows[0].cells: set_cell_border(cell, top{sz: 18, val: single, color: 000000}) # 底线最后一行 for cell in table