You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用openpyxl和Python3实现Excel/Calc的单元格复制功能?

当然可以用openpyxl实现你想要的完整复制功能!针对你提到的公式无法自动更新的问题,我整理了一套可行的解决方案,完全适配你的月度产量监控表格需求——既能复制单元格的样式、值、合并单元格,还能自动调整公式引用到目标位置。

核心思路

要实现你的需求,需要覆盖三个核心环节:

  • 复制单元格的样式属性(边框、对齐方式、数字格式等)和内容
  • 将原区域的合并单元格映射到目标位置
  • 解析原公式中的单元格引用,根据偏移量调整为目标区域的新引用(区分绝对/相对引用)
完整实现代码

首先导入必要的模块,然后编写工具函数和主复制函数:

from openpyxl import load_workbook
from openpyxl.utils import get_column_letter, column_index_from_string
import re
import copy

def adjust_formula_references(formula, source_start_r, source_start_c, target_start_r, target_start_c):
    """调整公式中的单元格引用,适配目标位置"""
    # 计算行和列的偏移量(基于0索引)
    row_offset = target_start_r - source_start_r
    col_offset = target_start_c - source_start_c

    # 匹配所有A1格式的单元格引用(支持绝对引用$)
    cell_ref_pattern = re.compile(r'(\$?[A-Z]+)(\$?\d+)')

    def replace_match(match):
        col_part, row_part = match.groups()

        # 处理列引用:绝对引用(带$)不偏移,相对引用偏移
        if col_part.startswith('$'):
            new_col = col_part
        else:
            col_idx = column_index_from_string(col_part)
            new_col_idx = col_idx + col_offset
            new_col = get_column_letter(new_col_idx)

        # 处理行引用:绝对引用不偏移,相对引用偏移
        if row_part.startswith('$'):
            new_row = row_part
        else:
            row_num = int(row_part)
            new_row_num = row_num + row_offset
            new_row = str(new_row_num)

        return f"{new_col}{new_row}"

    # 替换公式中所有匹配的引用
    return cell_ref_pattern.sub(replace_match, formula)

def copy_cell_range(template_sheet, target_sheet, source_range, target_start_cell):
    """
    复制指定单元格区域到目标位置
    :param template_sheet: 源模板工作表
    :param target_sheet: 目标工作表
    :param source_range: 源区域字符串,如"A1:E5"
    :param target_start_cell: 目标起始位置的0索引元组,如(0,0)=A1,(7,3)=D8
    """
    # 解析源区域的起始/结束位置(转换为0索引)
    source_start, source_end = source_range.split(':')
    source_start_c = column_index_from_string(source_start[:-1]) - 1
    source_start_r = int(source_start[-1:]) - 1
    source_end_c = column_index_from_string(source_end[:-1]) - 1
    source_end_r = int(source_end[-1:]) - 1

    target_start_r, target_start_c = target_start_cell

    # 1. 处理合并单元格
    for merged_range in template_sheet.merged_cells.ranges:
        # 检查当前合并区域是否在源范围内
        if (merged_range.min_row -1 >= source_start_r 
            and merged_range.max_row -1 <= source_end_r 
            and merged_range.min_col -1 >= source_start_c 
            and merged_range.max_col -1 <= source_end_c):
            # 计算目标位置的合并区域(转换为1索引)
            new_min_r = merged_range.min_row + (target_start_r - source_start_r)
            new_max_r = merged_range.max_row + (target_start_r - source_start_r)
            new_min_c = merged_range.min_col + (target_start_c - source_start_c)
            new_max_c = merged_range.max_col + (target_start_c - source_start_c)
            target_sheet.merge_cells(start_row=new_min_r, start_column=new_min_c, end_row=new_max_r, end_column=new_max_c)

    # 2. 遍历每个单元格,复制属性和内容
    for r in range(source_start_r, source_end_r + 1):
        for c in range(source_start_c, source_end_c + 1):
            source_cell = template_sheet.cell(row=r+1, column=c+1)  # openpyxl使用1索引
            # 计算目标单元格的1索引位置
            target_r = r + (target_start_r - source_start_r) + 1
            target_c = c + (target_start_c - source_start_c) + 1
            target_cell = target_sheet.cell(row=target_r, column=target_c)

            # 复制样式属性
            target_cell.border = copy.copy(source_cell.border)
            target_cell.alignment = copy.copy(source_cell.alignment)
            target_cell.number_format = source_cell.number_format

            # 处理值和公式
            if source_cell.data_type == 'f':
                # 调整公式引用后赋值
                adjusted_formula = adjust_formula_references(
                    source_cell.value, 
                    source_start_r, source_start_c, 
                    target_start_r, target_start_c
                )
                target_cell.value = adjusted_formula
            else:
                # 普通值直接复制
                target_cell.value = source_cell.value
使用示例

以下是针对你的月度报表场景的使用示例:

# 加载包含模板的工作簿
wb = load_workbook('monthly_template.xlsx')
template_sheet = wb['产量模板']

# 为5月创建新工作表,并复制模板区域到目标位置(比如从D8开始,对应0索引(7,3))
may_sheet = wb.create_sheet('2024年5月')
copy_cell_range(template_sheet, may_sheet, "A1:E5", (7, 3))

# 为6月创建新工作表,复制到不同位置(比如从B2开始,对应0索引(1,1))
june_sheet = wb.create_sheet('2024年6月')
copy_cell_range(template_sheet, june_sheet, "A1:E5", (1, 1))

# 保存最终工作簿
wb.save('2024_monthly_production.xlsx')
关键细节说明
  • 公式引用调整:通过正则表达式匹配所有单元格引用,区分绝对引用(带$)和相对引用,只偏移相对引用部分,完全符合Excel的默认行为。
  • 样式复制:使用copy.copy复制边框和对齐方式,避免因对象引用导致的样式同步问题。
  • 合并单元格处理:先判断原合并区域是否在源范围内,再计算偏移后的新区域,确保只复制需要的合并单元格。

内容的提问来源于stack exchange,提问作者AFoeee

火山引擎 最新活动