如何用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




