Python openpyxl合并Excel单元格:merge_cells用法与示例

Mason
Mason
发布时间:2026-11-13| 3人浏览过
原创

详细讲解使用Python openpyxl库合并Excel单元格的用法与技巧。涵盖merge_cells字符串与数字坐标两种调用形式、左上角值保留规则、样式与边框遍历处理方法以及unmerge_cells取消合并操作和完整代码示例。

Python openpyxl合并Excel单元格:merge_cells用法与示例
图 0:openpyxl 单元格合并底层架构与数据路由机制概览

在使用 Python 处理 Excel 报表时,跨列展示统计标题或将相同属性的多行数据聚合展示是常见的排版需求。在 openpyxl 库中,合并单元格的核心方法是 Worksheet.merge_cells()。它支持使用类似 'A1:C2' 的字符串区域标记,也支持传入行列的起始与结束整数坐标。需要注意的是,Excel 规范中合并区域仅由左上角单元格保存有效数据,后续写入、读取及边框样式的设置都围绕这一机制展开。掌握其调用方式与底层特性,能够避免常见的边框断裂与数据丢失问题。

一、merge_cells 的两种调用方式与参数说明

Worksheet.merge_cells() 提供了两种调用形式,分别适用于固定模板与动态计算场景:

  • 字符串范围形式(range_string):直接传入符合 Excel 习惯的范围字符串,如 ws.merge_cells('A1:D1')。这种方式直观简短,非常适合固定格式的表头或静态单元格区块。
  • 四元整数坐标形式:通过明确指定 start_row、start_column、end_row 和 end_column 四个关键字参数来进行合并。在通过循环遍历列表动态输出表格时,使用数值坐标能够直接参与变量运算,无需手动将数字转换为列字母。
Python openpyxl合并Excel单元格:merge_cells用法与示例
图 1:merge_cells 的两种传参形式与 Excel 坐标映射示意图

在数值坐标方式中,行号与列号均从 1 开始计数(1-based 索引),第 1 行第 1 列对应单元格 A1。若传入 0 或负数,openpyxl 将抛出 ValueError 异常。

以下代码演示了两种合并方式的基本写法:

import openpyxl

wb = openpyxl.Workbook()
ws = wb.active
ws.title = "合并示例"

# 方式一:使用字符串区域合并 A1 到 D1
ws.merge_cells('A1:D1')
ws['A1'] = "2024年度销售业绩分析表"

# 方式二:使用行列坐标参数合并第 3 行至第 4 行、第 1 列至第 2 列(即 A3:B4)
ws.merge_cells(start_row=3, start_column=1, end_row=4, end_column=2)
ws.cell(row=3, column=1, value="华东大区汇总")

wb.save("merge_demo.xlsx")

二、核心机制:左上角数据保留与读取陷阱

很多初学者在使用合并单元格时会遇到“写入数据不显示”或“读取合并单元格内容为 None”的困惑。这与 Excel 和 openpyxl 底层的存储模型直接相关:

  • 仅左上角单元格持有实体值:在一个合并区域中,只有左上角单元格(Top-Left Cell)是真正的物理存储载体。区域内的其余单元格在 openpyxl 内存结构中会被标记为从属的 MergedCell 对象。
  • 非左上角单元格的值会被遮盖:如果在合并操作之前,区域内的其他单元格已有内容,执行合并后这些内容在 Excel 界面上将被隐藏,且再次保存打开时可能会被清空或提示丢失。
  • 读取时的 None 陷阱:当使用代码遍历读取表格时,若直接访问合并区域内的非左上角坐标(例如已合并 A1:B2,尝试读取 ws['B1'].value 或 ws['A2'].value),返回的结果均为 None。
Python openpyxl合并Excel单元格:merge_cells用法与示例
图 2:合并前后物理存储结构与从属单元格只读遮盖示意图
避坑提示:数据写入与读取原则

为合并单元格赋值时,务必将值赋给左上角单元格(例如 ws['A1'] = "内容" 或 ws.cell(row=1, column=1, value="内容"))。如果要在后续脚本中读取合并单元格的值,建议通过 ws.merged_cells.ranges 检索单元格所属区域,或者在读取业务数据前建立坐标映射。

三、样式设置:文本居中对齐与全区域边框绘制

合并单元格后,对齐方式与边框的配置方式有着本质区别:

1. 对齐方式:仅需设置左上角

由于文本内容仅属于左上角单元格,水平与垂直居中只需要将 Alignment 赋给左上角单元格即可:

from openpyxl.styles import Alignment

# 设置左上角单元格水平与垂直双向居中
ws['A1'].alignment = Alignment(horizontal='center', vertical='center')

2. 边框样式:必须遍历整个区域

边框样式的设置机制与对齐截然不同。如果仅给左上角单元格设置边框(ws['A1'].border = ...),导出的 Excel 文件中只有合并区域的左边缘与上边缘有线条,右侧和底部将会失去边框,呈现出不闭合的残缺视觉。

这是因为 Excel 要求合并范围四周的每一个边缘单元格都必须具备各自方向上的边框属性。最为稳妥的做法是:遍历合并区域内的每一个行列坐标,统一为其赋予边框对象。

Python openpyxl合并Excel单元格:merge_cells用法与示例
图 3:合并区域边框与对齐设置的技术流程及单格边框缺陷对比

边框设置的完整辅助函数与应用示例如下:

from openpyxl.styles import Font, PatternFill, Alignment, Border, Side

def style_merged_range(ws, cell_range, font=None, fill=None, border=None, alignment=None):
    '''
    为合并单元格区域统一应用样式,确保全边缘边框闭合
    :param ws: 工作表对象
    :param cell_range: 字符串范围,如 'A1:D2'
    '''
    # 提取区域边界
    first_cell = ws[cell_range.split(':')[0]]
    if font:
        first_cell.font = font
    if fill:
        first_cell.fill = fill
    if alignment:
        first_cell.alignment = alignment

    # 遍历合并区域内的每一个单元格,补齐边框与底色
    if border or fill:
        for row in ws[cell_range]:
            for cell in row:
                if border:
                    cell.border = border
                if fill:
                    cell.fill = fill

四、取消单元格合并:unmerge_cells 的使用

当需要还原表格结构或拆分原本合并的区域时,可以使用 Worksheet.unmerge_cells() 方法。它的参数形式与 merge_cells() 完全一致,同样支持字符串范围与行列坐标:

# 方式一:传入字符串区域取消合并
ws.unmerge_cells('A1:D1')

# 方式二:传入坐标参数取消合并
ws.unmerge_cells(start_row=3, start_column=1, end_row=4, end_column=2)
取消合并后的数据分布特点

执行 unmerge_cells() 后,原本左上角单元格保留原有的数据与公式,而原本被合并的其他单元格将变为空白单元格(值为 None),并不会自动将左上角的值复制填充到所有单元格中。如果业务需要填充拆分后的单元格,需要自行编写逻辑将左上角的值分发给区域内各格。

五、完整实战示例:生成规范的销售季度汇总表

下面是一个完整的可执行示例代码。该代码构建一个季度销售业绩表,涵盖大标题跨列合并、大区跨行合并、居中对齐、完整边框及背景色填充:

import openpyxl
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side

def create_sales_report():
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "季度业绩报表"

    # 定义常用样式
    title_font = Font(name="微软雅黑", size=14, bold=True, color="FFFFFF")
    header_font = Font(name="微软雅黑", size=11, bold=True)
    body_font = Font(name="微软雅黑", size=10)

    title_fill = PatternFill(start_color="6B88A6", end_color="6B88A6", fill_type="solid")
    header_fill = PatternFill(start_color="F1F5F9", end_color="F1F5F9", fill_type="solid")
    accent_fill = PatternFill(start_color="E9F2F0", end_color="E9F2F0", fill_type="solid")

    center_align = Alignment(horizontal="center", vertical="center")
    thin_side = Side(style="thin", color="CBD5E1")
    box_border = Border(left=thin_side, right=thin_side, top=thin_side, bottom=thin_side)

    # 1. 设置报表大标题:合并 A1:D1
    ws.merge_cells("A1:D1")
    ws["A1"] = "2024 年第一季度区域销售业绩汇总"
    ws["A1"].font = title_font
    ws["A1"].alignment = center_align

    # 为标题区域的所有格赋予背景色与边框
    for row in ws["A1:D1"]:
        for cell in row:
            cell.fill = title_fill
            cell.border = box_border

    # 2. 写入表头数据 (第 2 行)
    headers = ["业务大区", "责任省份", "季度目标 (万)", "实际完成 (万)"]
    for col_idx, text in enumerate(headers, start=1):
        c = ws.cell(row=2, column=col_idx, value=text)
        c.font = header_font
        c.fill = header_fill
        c.alignment = center_align
        c.border = box_border

    # 3. 写入业务明细数据 (第 3 行至第 6 行)
    data = [
        ("华东大区", "江苏省", 1200, 1350),
        ("华东大区", "浙江省", 1100, 1280),
        ("华北大区", "北京市", 900, 960),
        ("华北大区", "河北省", 600, 620),
    ]

    for row_idx, row_data in enumerate(data, start=3):
        for col_idx, val in enumerate(row_data, start=1):
            c = ws.cell(row=row_idx, column=col_idx, value=val)
            c.font = body_font
            c.border = box_border
            if col_idx in (3, 4):
                c.alignment = Alignment(horizontal="right", vertical="center")
            else:
                c.alignment = center_align

    # 4. 合并大区跨行单元格:华东 (A3:A4) 与 华北 (A5:A6)
    # 使用坐标方式合并 A3:A4
    ws.merge_cells(start_row=3, start_column=1, end_row=4, end_column=1)
    ws.cell(row=3, column=1).fill = accent_fill

    # 使用字符串方式合并 A5:A6
    ws.merge_cells("A5:A6")
    ws["A5"].fill = accent_fill

    # 确保合并后各格边框依然完整
    for cell_range in ["A3:A4", "A5:A6"]:
        for row in ws[cell_range]:
            for cell in row:
                cell.border = box_border

    # 5. 调整列宽使显示更协调
    column_widths = {'A': 14, 'B': 14, 'C': 16, 'D': 16}
    for col_letter, width in column_widths.items():
        ws.column_dimensions[col_letter].width = width

    # 保存工作簿
    output_filename = "sales_summary_report.xlsx"
    wb.save(output_filename)
    print(f"报表已生成并保存至 {output_filename}")

if __name__ == "__main__":
    create_sales_report()

六、常见问题排查与核心原则总结

在日常使用 merge_cells 组织复杂报表时,建议对照以下生命周期与排查要点进行自查:

Python openpyxl合并Excel单元格:merge_cells用法与示例
图 4:openpyxl 单元格合并生命周期流转与 4 项核心排查清单
常见问题表现 根本原因 正确排查与解决方法
抛出 ValueError: Row or column values must be at least 1 数字坐标使用了 0 开始的索引(如 start_row=0) 确认 openpyxl 的行列坐标为 1-based,起始行与起始列必须 ≥ 1。
合并区域的右侧或下边缘边框消失 仅给左上角单元格赋予了边框,从属格未设置边框 双重循环遍历 ws[cell_range],为区域内的每一个单元格设置 cell.border。
遍历行数据时获取到的单元格内容为 None 访问到了合并区域中的非左上角从属单元格 读取数据前通过 ws.merged_cells.ranges 判断归属,统一提取对应范围的左上角单元格值。
Excel 提示“发现无法读取的内容”或文件损坏 多次合并产生了重叠或交叉的范围(如同时合并 A1:B2 与 B2:C3) 检查业务逻辑中的坐标范围,确保不同合并区域之间边界独立,无交叉重合。
相关专题:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。