在实际业务中,我们经常需要把表格导出为 Excel,并让它自带“高亮效果”。比如销售报表要标记超标数据、成绩表要显示高低分区间、排班表要区分单双周。如果这些操作停留在手工阶段,每次数据更新后都需要反复重复设置,效率会非常低。
Python 的openpyxl库可以帮助我们直接操作 Excel 文件,把“条件格式”写入工作表的指定区域。数据一旦更新,条件格式依然存在,配合 Pandas 批量处理数据非常高效。
今天这篇教程,将围绕Python 操作 Excel 条件格式展开,分为三个常用场景:
- 高亮交替行颜色,提升长表格可读性;
- 高亮每个科目/列中的最大值和最小值;
- 根据数值范围(比如 Total Score 在某区间内)动态填充颜色。
除此之外,还会介绍色阶(Color Scale)、最低分/不及格标注等延伸技巧,并提供一份完整可运行的 Python 脚本,帮你在真实项目中落地。
本文适合这样的读者:
- 会 Python 基础语法,但不太熟悉
openpyxl条件格式 API 的开发者; - 需要批量生成 Excel 报表、数据分析结果导出文件等技术同学;
- 想减少手工处理 Excel 格式、把重复步骤自动化的效率爱好者。
学完本文后,你能够独立完成“创建 Excel → 写入数据 → 添加条件格式 → 保存文件 → 验证效果”的完整闭环。
1. Excel 条件格式到底能做什么
1.1 为什么不用传统单元格填色
很多同学第一反应是:给某个单元格填充颜色,直接用PatternFill不就好了?
from openpyxl.styles import PatternFill fill = PatternFill(start_color='FFFF00', end_color='FFFF00', fill_type='solid') ws['A1'].fill = fill这种方式确实“能上色”,但它是静态的。比如:你先把 A1 的值设置成 90,然后填黄色,之后 A1 变成 50,黄色永远不会消失。可如果 Excel 里使用的是真正的条件格式,单元格颜色会根据值的变化自动更新,完全不需要重新执行 Python 脚本。
换句话说:
- 静态填色适合“输出时已经确定结果”的场景;
- 条件格式适合“交付后仍希望 Excel 自动响应数据变化”的场景。
当我们用 Python 生成模板、报表,或者给同事交付一份有筛选/输入属性的 Excel 时,条件格式才是更贴近真实需求的技术方案。
1.2 条件格式在业务中的常见应用场景
条件格式不是 Excel 的炫技功能,它解决的是**“数据可视化 + 数据提醒”**问题。
| 场景 | 条件格式策略 | 业务收益 |
|---|---|---|
| 销售业绩表 | 高亮超过 100 万的记录 | 快速定位超额订单 |
| 学生成绩单 | 标红不及格科目 | 直观展示薄弱科目 |
| 库存报表 | 对低于安全库存的产品填色 | 及时补货 |
| 日程排班 | 区分奇偶行或班次类型 | 减少误读 |
| 财务对账 | 高亮差异大于阈值的行 | 快速复核重点项目 |
这类格式变化如果交给业务人员手工完成,不仅耗时,还容易遗漏;如果和数据逻辑混在一个脚本里,后续维护也很痛苦。比较规范的做法是:Python 只负责生成数据和写入规则,Excel 本身负责数据变化后的自动展现。
1.3 本文的技术边界
本文使用openpyxl实现条件格式,主要会接触以下 API:
Worksheet.conditional_formatting.add()CellIsRuleFormulaRuleColorScaleRulePatternFill、Font、Border
不需要使用 VBA 或 Excel COM 组件。只要安装了 Python 和openpyxl,就可以跨平台运行,适合 Linux 服务器上的报表服务和 CI/CD 流程。
2. 环境准备与项目目录
2.1 安装 Python 和 openpyxl
如果本机还没有安装 Python,请先安装 Python 3.8 或以上版本。命令行中输入:
python --version确认有 Python 环境后,执行:
pip install openpyxl如果希望和 Pandas 配合,可以安装:
pip install pandas openpyxlopenpyxl是 Excel.xlsx文件操作库,它不依赖 Excel 软件本身。因此哪怕是在 Linux 服务器上,也可以生成带条件格式的 Excel 文件。
2.2 示例项目结构
为了让教程更贴近实际开发,我建议按下面结构试验:
excel_conditional_formatting/ ├── output/ ├── create_report.py └── 需求说明.mdcreate_report.py是我们要运行的主脚本,output/目录存放生成的 Excel 文件。
如果你暂时不想创建项目目录,也可以把 Python 脚本放在任意位置,只要修改保存路径即可。本文示例将演示完整代码,方便拷贝执行。
2.3 openpyxl 版本说明
openpyxl的conditional_formatting相关 API 从 2.5 版本后已相对稳定。本文示例基于openpyxl 3.x的通用写法。
由于各版本 API 细节略有差异,如果你的代码运行报错,请优先执行:
pip install --upgrade openpyxl来确保依赖版本不落后于示例。
3. openpyxl 条件格式核心概念
3.1 最小插入流程
使用openpyxl给 Excel 添加条件格式,本质上只有两步:
- 构造一个Rule 规则对象;
- 把这个规则通过
conditional_formatting.add()应用到某个单元格区域。
例如,我们要让 A1:A10 中大于 80 的值显示绿色:
from openpyxl import Workbook from openpyxl.formatting.rule import CellIsRule from openpyxl.styles import PatternFill wb = Workbook() ws = wb.active # 写入测试数据 for i in range(1, 11): ws[f'A{i}'] = i * 10 green_fill = PatternFill(start_color='C6EFCE', end_color='C6EFCE', fill_type='solid') # 构造条件格式规则,大于 80 时应用公式或格式 ws.conditional_formatting.add( 'A1:A10', CellIsRule( operator='greaterThan', formula=['80'], fill=green_fill ) ) wb.save('demo1.xlsx')运行这段代码后,打开demo1.xlsx,数据大于 80 的单元格会自动变为绿底。如果手动把 82 改成 60,颜色也会立刻消失,这就是“条件格式”与“静态填充”的最大区别。
3.2 理解“规则作用区域”的字符串
ws.conditional_formatting.add(区域, 规则)的“区域”可以是如下形式:
'A1:A10':单列单元格区域;'A1:F100':矩形区域;'A1:F1':单行区域;'A:C':整列区域。
这里的区域是指规则的作用范围,并不等于公式参与运算的范围。公式仍然由表达式中的引用所决定。
例如:
ws.conditional_formatting.add( 'A1:A10', CellIsRule( operator='greaterThan', formula=['=MAX($A$1:$A$10)'], # 也可以不带等号 fill=green_fill ) )这个规则作用在 A1:A10,判断每个单元格是否大于 A1:A10 的最大值。虽然我们很少这么做,但概念上要区分清楚。
3.3 Rule 表达式中是否带等号
很多同学在写FormulaRule时经常困惑:公式里要不要带一个等号?
在openpyxl文档和多数示例中,formula参数可以写['MOD(ROW(),2)=0'],不带头等号,也可以写成['=MOD(ROW(),2)=0'],效果等价。为了保险起见,统一建议不写等号,如下:
FormulaRule(formula=['MOD(ROW(),2)=0'])如果写CellIsRule,公式只填入数值或函数表达式:
CellIsRule(operator='between', formula=['60', '90'])最保险的做法是在本地 Excel 里先录制一个条件格式,再查看对应 XML,但本文不会深入到 XML 层面。按照上述写法,绝大多数场景都可正常运作。
3.4 动态区域推荐使用 Excel 表而不是开放引用
如果你写的是:
ws.conditional_formatting.add('A1:A100', ...)当数据超过 100 行时,新增行不会自动应用规则。实际项目中建议给 Excel 数据区域插入“表格(Table)”,或者把范围写得足够大,例如A1:F10000,但过大范围会影响文件体积和运算性能。
如果使用openpyxl让你手动控制范围,推荐先获得数据行数:
max_row = ws.max_row ws.conditional_formatting.add(f'A1:F{max_row}', ...)这样在写数据之后添加规则,能天然匹配当前数据量。
4. 实战案例:学生成绩表如何加条件格式
为了让三个核心需求更有代入感,我设计一个学生成绩表案例。
4.1 业务需求
有一份学生成绩单,包含字段:
| 姓名 | 语文 | 数学 | 英语 | 总分 |
|---|---|---|---|---|
| 张三 | 88 | 92 | 76 | 256 |
| 李四 | 70 | 85 | 90 | 245 |
| ... | ... | ... | ... | ... |
交付前需要实现 5 个可视化动作:
- 整行隔行变色,让长表格更易读;
- 每科(语文、数学、英语)的最高分用绿色背景高亮;
- 每科最低分用橙色背景高亮;
- 总分在 240~270 分区间的记录,所在行添加黄色底纹;
- 总分低于 200 的单元格标红。
4.2 创建基础数据与表头
我们先新建一个脚本文件:create_report.py,写入导入和数据初始化。
# 文件路径:excel_conditional_formatting/create_report.py from openpyxl import Workbook from openpyxl.styles import PatternFill, Font, Alignment, Border, Side from openpyxl.formatting.rule import CellIsRule, FormulaRule from openpyxl.utils import get_column_letter wb = Workbook() ws = wb.active ws.title = "成绩分析" # 表头 headers = ["姓名", "语文", "数学", "英语", "总分"] ws.append(headers) # 模拟数据 raw_data = [ ("张三", 88, 92, 76, 256), ("李四", 70, 85, 90, 245), ("王五", 60, 66, 58, 184), ("赵六", 95, 80, 88, 263), ("孙七", 82, 91, 73, 246), ("周八", 79, 64, 85, 228), ("吴九", 93, 98, 96, 287), ("郑十", 58, 72, 69, 199), ("冯十一", 74, 89, 61, 224), ("陈十二", 90, 77, 82, 249), ] for row in raw_data: ws.append(list(row))表格结构为:
- 第 1 行:表头;
- 第 2~11 行:10 条学生成绩数据;
- 数据总行数:10;
- 最后一行行号:
ws.max_row,值为 11。
4.3 设计样式常量
我们首先要定义不同的填充色。实际项目中,你可以参考 Excel 内置条件格式颜色的十六进制值。
# 样式常量 header_font = Font(bold=True, color="FFFFFF", size=11) header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") center_alignment = Alignment(horizontal="center", vertical="center") # 交替行颜色:浅蓝 alternate_fill = PatternFill(start_color="D9E1F2", end_color="D9E1F2", fill_type="solid") # 最高分颜色:绿底深绿字 max_fill = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid") max_font = Font(color="006100", bold=True) # 最低分颜色:橙底深橙字 min_fill = PatternFill(start_color="FFEB9C", end_color="FFEB9C", fill_type="solid") min_font = Font(color="9C6500", bold=True) # 总分范围:黄底 range_fill = PatternFill(start_color="FFF2CC", end_color="FFF2CC", fill_type="solid") # 低分红色:红底深红字 low_fill = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid") low_font = Font(color="9C0006", bold=True)注意:PatternFill的start_color和end_color在纯色填充下保持一致即可。为什么要写fill_type="solid"?因为 openpyxl 如果只是给出两个颜色值但不声明 solid,可能在 Excel 中显示不出纯色。
4.4 设置表头样式并冻结窗格
为了让交付效果更专业,我们可以顺手做:
# 设置表头样式 for col_idx, _ in enumerate(headers, start=1): cell = ws.cell(row=1, column=col_idx) cell.font = header_font cell.fill = header_fill cell.alignment = center_alignment # 设置列宽 for col_idx in range(1, len(headers) + 1): ws.column_dimensions[get_column_letter(col_idx)].width = 12 # 冻结首行,方便向下翻阅 ws.freeze_panes = "A2" # 表头加边框 thin_border = Border( left=Side(style="thin", color="B4C6E7"), right=Side(style="thin", color="B4C6E7"), top=Side(style="thin", color="B4C6E7"), bottom=Side(style="thin", color="B4C6E7"), ) for row in ws.iter_rows(min_row=1, max_row=ws.max_row, max_col=ws.max_column): for cell in row: cell.border = thin_border这里的代码虽然和条件格式不直接相关,但在一份真实交付文件中很常见,因为只有同时完善字体、列宽、对齐和边框,导出的表格才具备“可直接发给他人”的质量。
5. 高亮交替行颜色(斑马纹)
5.1 实现隔行变色的核心思路
交替行颜色的实现方案有很多种:
- 每写入一行数据,就对该行整行填充静态颜色;
- 使用条件格式,利用
ROW()函数判断当前行号奇偶; - 使用 Excel 表格的“带状行”功能(Table Style)。
本文重点讲第 2 种,因为它不需要重写数据,并且当用户插入/删除行时颜色也能自动适配(在一定范围内)。
条件公式如下:
MOD(ROW(),2)=1ROW():返回当前单元格所在行号;MOD(ROW(),2):行号对 2 取余;- 等于 1 表示奇数行,等于 0 表示偶数行。
如果第一行是表头,我们一般想让第 2、4、6... 行有底色,也就是偶数行。公式就应该是:
MOD(ROW(),2)=0也可以反过来,为奇数行填充浅色。重点是不要把条件区域包含表头,否则表头也可能被染色。
5.2 将斑马纹作用到整行
假设数据区域为 A2:E11,我们要让该区域内的偶数行变浅蓝色。
# 数据区域最后一行行号 last_row = ws.max_row # 对 A2:E11 添加条件格式 ws.conditional_formatting.add( f'A2:E{last_row}', FormulaRule( formula=['MOD(ROW(),2)=0'], fill=alternate_fill ) )这段代码会每行独立判断,理论上该规则受区域限制,只对 A 到 E 列对应行生效。如果要让整行(比如 A 到 G)显示,区域就要写到 G。
5.3 更安全的“从第三行开始”技巧
如果表格设计了标题行和合并单元格,通常我们不希望第一块数据行也被隔行染色。更灵活的做法是用“序号”列或辅助公式来判断。但最简单的是认清起点行号。
假设数据从第 3 行开始,那么该行需要染色时,条件公式使用MOD(ROW(),2)=1还是=0,取决于你想让第 3 行是否有颜色。如果第 3 行染色,那条件是:
MOD(ROW(),2)=1因为 3 对 2 取余等于 1。
这里需要特别提醒:不要盲目的复制网上的公式,先想清楚自己的表头在第几行、数据开始在哪一行、哪一行需要被高亮。否则会出现隔行颜色颠倒或从中间错位的问题。
5.4 可以搭配的“悬浮效果”思路
虽然 Excel 条件格式不太适合“鼠标悬浮行高亮”,但我们可以利用同样的公式原理实现“奇数列、偶数列”的交替颜色。例如将公式改成:
MOD(COLUMN(),2)=0就可以让 B、D、F... 列出现底色。这种写法适合做复杂的账单模板,但由于不是今天的重点,这里不展开。
6. 高亮最大值、最小值与范围值
6.1 最大值与最小值的常用规则
在 Excel 手工条件格式里,可以选择“仅对排名靠前或靠后的数值设置格式”,而CellIsRule更适合配合公式实现,因为在 openpyxl 的 API 中,我们可以提交一个函数表达式作为条件。
假设语文成绩在 B 列,范围是 B2:B11。语文最高分标记为绿色:
ws.conditional_formatting.add( f'B2:B{last_row}', CellIsRule( operator='greaterThanOrEqual', formula=[f'MAX($B$2:$B${last_row})'], fill=max_fill, font=max_font ) )operator='greaterThanOrEqual':表示“大于等于”;formula=[f'MAX($B$2:$B${last_row})']:用来求 B2:B11 的最大值;- 使用绝对引用
$B$2:$B$11,因为它不应该随单元格变化; - 规则应用到 B2:B11,循环比较每个单元格的值与该区域最大值。
同样的道理,数学最低分就用橙色标记:
# C列是数学 ws.conditional_formatting.add( f'C2:C{last_row}', CellIsRule( operator='lessThanOrEqual', formula=[f'MIN($C$2:$C${last_row})'], fill=min_fill, font=min_font ) )如果语文、数学、英语三科都要实现该效果,我们需要分别对 B、C、D 三列添加规则,否则使用同一个规则作用在多个列时,公式会比较混乱。
这段逻辑可以写为循环:
score_columns = { "B": "语文", "C": "数学", "D": "英语", } for col in score_columns: col_range = f"{col}2:{col}{last_row}" # 最高分 ws.conditional_formatting.add( col_range, CellIsRule( operator='greaterThanOrEqual', formula=[f'MAX(${col}$2:${col}${last_row})'], fill=max_fill, font=max_font ) ) # 最低分 ws.conditional_formatting.add( col_range, CellIsRule( operator='lessThanOrEqual', formula=[f'MIN(${col}$2:${col}${last_row})'], fill=min_fill, font=min_font ) )6.2 最大值、最小值出现多个相同值怎么办
如果一列中有两个相同的最高分,比如语文有两个 95 分,上面的规则会同时高亮这两个 95 分。这不是 bug,因为公式判断条件为“大于等于最大值”,所有等于最大值的单元格都会命中。
如果业务上只希望高亮第一个最大值,则需要配合COUNTIF或数组公式,但在 Excel 条件格式下实现比较复杂。大多数报表场景中,高亮所有最大值/最小值是更能接受的默认行为。
6.3 范围值:Between 与 指定范围
项目标题中的第三个需求是**“范围值”**,通常有两种理解:
- 单元格数值在某个区间内,比如总分在 240 到 270 之间时整行高亮;
- 单元格的数值加上颜色刻度(ColorScale),例如数值越大颜色越深。
先来看第一种:总分在 E 列,E2:E11。我们希望总分在 240~270 区间的行,其 A~E 列单元格都显示浅黄色。
如果只给 E 列加规则,那么只有总分单元格变色,不符合“整行高亮”的预期。所以我们需要将规则作用到 A2:E11,并使用公式进行跨列判断。
ws.conditional_formatting.add( f'A2:E{last_row}', FormulaRule( formula=[f'AND($E2>=240,$E2<=270)'], fill=range_fill ) )这里要注意:
- 区域虽然是 A2:E11,但 FormulaRule 中公式判断的是
$E2; $E2的含义是:列 E 绝对引用,行号相对引用;- 当规则作用到第 3 行单元格时会自动变为
$E3; AND() &&,一定不要把 Excel 里不支持&&写进去,必须使用AND()函数。
有了这个规则,总分介于 240~270 的学生,整行都会被黄色填充。
6.4 低于 200 分标红
同理,如果我们希望总分小于 200 的单元格用红色提示,可以这样写:
ws.conditional_formatting.add( f'E2:E{last_row}', CellIsRule( operator='lessThan', formula=['200'], fill=low_fill, font=low_font ) )如果希望整行标红,同样将区域扩大为 A~E 列:
ws.conditional_formatting.add( f'A2:E{last_row}', FormulaRule( formula=[f'$E2<200'], fill=low_fill, font=low_font ) )6.5 延伸:用 ColorScaleRule 实现双色/三色刻度
有时候“范围值”不是离散区间,而是一种连续视觉梯度。比如希望总分列颜色从高到低自然渐变,可以使用ColorScaleRule。
from openpyxl.formatting.rule import ColorScaleRule ws.conditional_formatting.add( f'E2:E{last_row}', ColorScaleRule( start_type='min', start_color='F8696B', # 红色 mid_type='percentile', mid_value=50, mid_color='FFEB84', # 黄色 end_type='max', end_color='63BE7B' # 绿色 ) )这段代码会给总分列添加三色刻度:最低分红色、中间分黄色、最高分绿色。启动 Excel 后看到的是一行渐变效果,比单纯高亮最大最小值更加直观。
注意:
ColorScaleRule的start_type、mid_type、end_type可以填写min、max、num、percent、percentile等。如果你只想要双色刻度,可以创建一个只有 start 和 end 的 ColorScaleRule。
6.6 汇总:本次实战的条件格式代码
把上述片段汇总后,保存文件部分的代码如下:
# 保存文件 output_path = "output/学生成绩条件格式.xlsx" import os os.makedirs("output", exist_ok=True) wb.save(output_path) print(f"文件已生成:{output_path}")如果你已经按顺序在同一个脚本里写完,现在可以运行:
python create_report.py运行后,在output/目录下找到学生成绩条件格式.xlsx,打开后可以看到第 2/4/6/8/10 行呈现浅蓝色隔行效果,各科最高分为绿色,最低分为橙色,总分 240~270 的整行是浅黄色,总分低于 200 的行是红色底纹。
7. 条件格式规则与表格样式冲突问题
7.1 手工填充和条件格式同时出现时的优先级
如果你在脚本中既用了PatternFill给某些单元格设了静态底色,又添加了条件格式,当条件格式命中时,Excel 会优先显示条件格式颜色。
举例:
ws['E2'].fill = PatternFill(start_color='FF0000', end_color='FF0000', fill_type='solid')随后如果 E2 满足“总分 240~270”条件,显示的并不是红色而是规则中设置的颜色。原因在于 Excel 的条件格式默认高于普通填充样式。
这既是优点也是坑:如果你希望某些单元格固定为一种颜色,就不要再给它们加条件格式,或者你给这些单元格所在区域额外加一个“停止如果为真”之类的策略。不过openpyxl对“停止如果为真”的控制较弱,生产环境建议在规则设计阶段就避免冲突。
7.2 区域重叠可能导致深浅色互相覆盖
例如我们有一个规则让 A2:E11 的偶数行变浅蓝,另一个规则让总分在 240~270 的行变黄色。这时第 4 行如果是总分 250,那么它同时满足两个条件。
Excel 默认按照条件格式规则的创建顺序依次判断,如果多个条件都为真,则使用第一个规则格式(在 Excel 界面中上面的规则优先)。因此在脚本中,如果希望范围色优先于隔行色,就要先添加隔行色,再添加范围色。
如果颜色表现不符合预期,可以通过调整代码中conditional_formatting.add()的调用顺序来解决。这也是一个很多新手查不出原因的坑。
7.3 需要看清条件格式公式的相对引用
FormulaRule(formula=['MOD(ROW(),2)=0'])中ROW()没有带单元格前缀,所以它会跟随“当前单元格”变化。如果你在公式中写成了MOD($A$2,2)=0,那么所有单元格都会拿 A2 的行号去判断,最终导致整片区域要么全变色,要么全不变。
所以在编写条件格式时,请反复检查:
- 需要“当前行变化”的地方,不要加
$行号; - 需要“锁定某列/某区域”的地方,不要漏掉
$。
一个小技巧是:在公式中想象“当前单元格是被选中单元格中的左上角”,然后基于相对位置推导公式。如果你对公式本身不自信,可以先在 Excel 中手写一条条件格式并保存,再用 Python 的只读模式分析其 XML,或者直接打开生成的 xlsx 和预期效果对照。
8. 常见问题与排查思路
这是一个高频问题汇总表,建议遇到问题时优先对照排查。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 生成的 Excel 打开后提示文件损坏 | openpyxl版本过低或代码保存路径写入异常 | 升级 openpyxl,优先用wb.save()到新路径;不要同时被 Excel 打开后再覆盖 |
| 条件格式没有生效 | 添加规则的区域和实际数据区域不一致 | 确认规则区域包含要作用的单元格,数据行号用max_row动态计算 |
| 只有第一个单元格变色 | 公式相对引用/绝对引用设置错误 | 检查公式中是否少了$或多加了$;整行判断要使用如$E2<200 |
| 整个区域全部变色 | 公式没有正确针对当前单元格 | 例如公式写成ROW(A1)=ROW(),但区域过大导致逻辑错位;需要简化成MOD(ROW(),2)=0 |
| 交替行颜色和范围色重叠时显示不符合预期 | 同时满足多个条件,不清楚 Excel 规则优先级 | 调整add()的顺序,或拆分规则区域 |
| 颜色很浅或显示不出来 | PatternFill没有设置fill_type='solid' | 所有纯色填充统一补上fill_type='solid' |
| 修改 Excel 数据后条件格式不会自动扩展 | 规则作用范围固定为 A1:A100,新增行不在范围 | 生成文件时给规则范围预留足够行数,或使用 Excel 表格对象 |
| WPS/LibreOffice 打开显示效果不一致 | 办公软件对条件格式的公式解析存在差异 | 以 Excel 打开为最终验收标准,涉及复杂公式时先在 Excel 验证 |
8.1 表格内容为空时,规则仍然存在
openpyxl 只负责将规则写入 XML 文件。即使 A1:F10000 范围内没有数据,规则依然存在,Excel 打开后不会报错。但大范围的条件格式会明显增大文件体积,建议生成前先通过ws.max_row获取有效行数。
8.2 使用 Pandas 读取 Excel 后要不要重写规则
如果你的完整流程是:
Excel读取(openpyxl) → pandas处理 → Excel保存
那么使用 pandas 默认方式保存文件,可能会丢失原来的条件格式。因为 pandas 不会解析并保留所有 openpyxl 对象。解决方案有两种:
- 先用
openpyxl打开原文件,拿到数据后自行处理,最后wb.save(),保留原规则; - 不要把规则和数据写在一起,先生成新的干净文件再统一添加规则。
推荐第二种,逻辑更清晰,也方便测试。
9. 进阶:封装一个可复用的条件格式管理器
课程只贴代码不讲封装,不利于工程落地。如果项目中有多种报表需要反复使用条件格式,我们可以把这些方法提取到类中。下面我给一个简化版工具类示例。
# 文件路径:excel_conditional_formatting/excel_formatter.py from openpyxl.formatting.rule import CellIsRule, FormulaRule, ColorScaleRule from openpyxl.styles import PatternFill class ExcelConditionalFormatter: def __init__(self, ws): self.ws = ws @staticmethod def make_fill(hex_color: str) -> PatternFill: return PatternFill( start_color=hex_color, end_color=hex_color, fill_type="solid" ) def add_row_striping(self, cell_range: str, color: str = "D9E1F2", even: bool = True) -> None: """为区域添加斑马纹,even=False 表示奇数行变色。""" fill = self.make_fill(color) if even: formula = ["MOD(ROW(),2)=0"] else: formula = ["MOD(ROW(),2)=1"] self.ws.conditional_formatting.add( cell_range, FormulaRule(formula=formula, fill=fill) ) def add_max_min_highlight(self, col_letter: str, start_row: int, end_row: int, max_color: str = "C6EFCE", min_color: str = "FFEB9C") -> None: """对某一列添加最大值绿色、最小值橙色。""" col_range = f"{col_letter}{start_row}:{col_letter}{end_row}" max_fill = self.make_fill(max_color) min_fill = self.make_fill(min_color) self.ws.conditional_formatting.add( col_range, CellIsRule( operator="greaterThanOrEqual", formula=[f"MAX(${col_letter}${start_row}:${col_letter}${end_row})"], fill=max_fill ) ) self.ws.conditional_formatting.add( col_range, CellIsRule( operator="lessThanOrEqual", formula=[f"MIN(${col_letter}${start_row}:${col_letter}${end_row})"], fill=min_fill ) ) def add_range_by_formula(self, cell_range: str, condition_formula: str, color: str = "FFF2CC") -> None: """通过自定义公式添加一个范围/整行规则。""" self.ws.conditional_formatting.add( cell_range, FormulaRule(formula=[condition_formula], fill=self.make_fill(color)) )使用方式:
formatter = ExcelConditionalFormatter(ws) formatter.add_row_striping(f"A2:E{last_row}", even=True) formatter.add_max_min_highlight("B", 2, last_row) formatter.add_range_by_formula(f"A2:E{last_row}", "$E2>=240", "FFF2CC")封装之后,主报表脚本会清爽很多,后续扩展指定条件只改一行即可。
10. 最佳实践与工程建议
10.1 优先写出数据,再统一添加条件格式
建议流程:
- 先准备数据,完成基础表格;
- 统一添加表头、边框、列宽等样式;
- 最后再添加条件格式规则。
原因很简单:条件公式中经常需要引用ws.max_row,如果先加规则再写数据,max_row可能只停留在 1,导致规则区域错误。
10.2 将条件格式颜色定义统一常量
条件格式涉及的颜色比较多,例如:
- 绿底
C6EFCE+ 深绿字006100; - 黄底
FFEB9C+ 深黄字9C6500; - 红底
FFC7CE+ 深红字9C0006。
这些是 Excel 内置条件格式的经典颜色组合。建议把它们放在文件头部或用单独的constants.py管理,避免在代码中写散落的十六进制值。如果使用方要求品牌色或公司色,后续只需要改一处。
10.3 测试规则时不要随便覆盖原文件
我在实际项目中习惯先将文件保存到output/xxx_demo.xlsx,人工打开验证效果并通过后再决定是否覆盖生产模板。
不要在生产目录下直接执行wb.save(),特别是在 Windows 上文件被 Excel 占用时,写入会报PermissionError。稳妥做法是生成到临时文件再替换。
10.4 文件体积和数据量控制
条件格式规则的数量如果过多,每个规则都应用到一个很大的区域(比如 10 万行),文件打开和运算都会变慢。此时更推荐两种方案:
- 把数据量控制到 Excel 合理范围,再添加条件格式;
- 使用 Excel 表格(Table)或 Power Query 的思路,在进入 Excel 前先完成聚合。
对于百万行级别的数据,Excel 本来就不是最优展示载体,此时应考虑导出 CSV 或使用 BI 工具。
10.5 理解 Excel 条件格式的“设计时”和“运行时”
条件格式是“设计时生成规则、运行时自动计算”的机制。写 Python 脚本的人必须理解:我们不是在生成一张静态图片,而是在生成一个有智慧的 Excel 模板。
因此,交付文档同时要提醒使用者:
- 不要随意删除条件格式规则;
- 不要手动给单元格涂色后抱怨颜色不固定;
- 如果需要扩展数据,建议保留一定空白规则范围,或者先插入 Excel 表格。
10.6 安全与权限提醒
如果你在一个自动化报表流程里,通过 Python 操作公司共享盘上的 Excel 或把生成文件上传到内部系统:
- 一定要给脚本加配置文件和日志,不要硬编码磁盘路径;
- 不要把账号口令写在脚本里;
- 生成文件前确认是否有写入权限;
- 如果脚本会自动修改线上模板或原始数据文件,务必先备份,并在测试环境验证。
这些看起来和条件格式无关,但真正在团队协作中使用时会成为最容易被忽略的生产事故点。
11. 更多可以继续探索的方向
openpyxl的条件格式功能范围比本文示例更大,官方还支持数据条(DataBar)、图标集(IconSet)、Top10、唯一值/重复值等规则。如果你想继续深入,可以按以下清单逐个练习:
| 规则类型 | openpyxl 相关类 | 适用场景 |
|---|---|---|
| 数据条 | DataBarRule | 显示业绩长度比较,直观但占空间 |
| 图标集 | IconSetRule | 用箭头/红绿灯表现升降和健康状态 |
| Top10 规则 | Top10Rule | 标记销售前 10 名 |
| 唯一值/重复值 | Rule(type='duplicateValues') | 清洗数据时快速定位重复记录 |
| 包含文本 | CellIsRule(operator='containsText') | 对文本列做关键字标记 |
在实际项目中,我会根据业务场景灵活组合这些规则。比如销售看板用“数据条 + Top10”、考试成绩用“最大值/最小值 + 范围值”、通讯录清洗用“重复值高亮”。
如果你长期处理“Python + Excel”的自动化任务,建议把条件格式纳入你的技能清单,因为它能让交付的 Excel 从“一张数据表”变成“一个自动可视化的报表工具”。哪怕使用者完全不懂公式,也能从颜色中一眼看出问题和高价值数据。
下一步可以做什么?动手把create_report.py里的模拟数据替换成你手头的真实数据,运行后打开 Excel,观察三组核心规则是否达到预期。如果颜色没有出现,优先检查规则区域行号是否正确,然后检查fill_type="solid"是否漏写。只要把这一套跑通,你就能体会到用 Python 控制 Excel 条件格式的效率优势了。