简体   繁体   English

openPyXL - 在取消合并期间为单元格范围赋值

[英]openPyXL - assign value to range of cells during unmerge

So I have excel files with several sheets in each and I'm working on script which will gather data from selected sheets if they exist in file and combine it in one, big sheet.所以我有一个 excel 文件,每个文件都有几个工作表,我正在编写脚本,如果它们存在于文件中,它将从选定的工作表中收集数据并将其合并到一个大工作表中。 Generally it's working, iterating through files and if desired sheet exist it finds range of cells with data and append it to dataframe.通常它正在工作,遍历文件,如果存在需要的工作表,它会找到包含数据的单元格范围并将其附加到数据帧。 The thing I need to do now is to add header row (column names) to Dataframe, but in sheet those are multiline headers.我现在需要做的是向 Dataframe 添加标题行(列名),但在工作表中这些是多行标题。

To make it look the same in dataframe i need to unmerge cells in top header row and copy value from first cell to rest of them in range which was merged before).为了使其在数据框中看起来相同,我需要取消合并顶部标题行中的单元格,并将值从第一个单元格复制到之前合并的范围内的其余单元格)。

I'm using OpenPyXL for accessing excel sheets.我正在使用 OpenPyXL 来访问 Excel 表格。 My function receives sheet to work on as only parameter.我的函数接收工作表作为唯一参数。 It looks like this:它看起来像这样:

def checkForMergedCells(sheet):
    merged = ws.merged_cell_ranges
    for mergedCell in merged:
        mc_start, mc_stop = str(mergedCell).split(':')
        cp_value = sheet[mc_start]
        sheet.unmerge_cells(mergedCell)
        cell_range = sheet[mergedCell]
        for cell in cell_range:
            cell.value = cp_value

The thing is that cell_range returns a tuple which ends up in getting error message:问题是 cell_range 返回一个元组,最终得到错误消息:

AttributeError: 'tuple' object has no attribute 'value' Below you can see screencap during debug which shows values passed in each variable. AttributeError: 'tuple' object has no attribute 'value' 下面您可以在调试期间看到屏幕截图,其中显示了在每个变量中传递的值。

Debugger running调试器运行

Accessing by index will generally return a tuple of tuples except if you try to get an individual cell or row.按索引访问通常会返回一个元组元组,除非您尝试获取单个单元格或行。 For programmatic access you should use iter_rows() or iter_cols()对于编程访问,您应该使用iter_rows()iter_cols()

You might like to spend some time looking at the utils module.您可能想花一些时间查看utils模块。

from openpyxl.utils import range_boundaries

for group in ws.merged_cell_ranges:
     min_col, min_row, max_col, max_row = range_boundaries(group)
     top_left_cell_value = ws.cell(row=min_row, column=min_col).value
     for row in ws.iter_rows(min_col=min_col, min_row=min_row, max_col=max_col, max_row=max_row):
         for cell in row:
             cell.value = top_left_cell_value

None of the previous answers works.以前的答案都不起作用。 So I elaborated this one, tested it and it worked for me.所以我详细阐述了这个,测试了它,它对我有用。

from openpyxl.utils import range_boundaries
wb = load_workbook('Example.xlsx')

sheets = wb.sheetnames  ##['Sheet1', 'Sheet2']
for i,sheet in enumerate(sheets):
    ws = wb[sheets[i]]
    
    # you need a separate list to iterate on (see explanation #2 below)
    mergedcells =[]  
    for group in ws.merged_cells.ranges:
        mergedcells.append(group)
    
    for group in mergedcells:
        min_col, min_row, max_col, max_row = group.bounds 
        top_left_cell_value = ws.cell(row=min_row, column=min_col).value
        ws.unmerge_cells(str(group))   # you need to unmerge before writing (see explanation #1 below)
        for irow in range(min_row, max_row+1):
            for jcol in range(min_col, max_col+1): 
                ws.cell(row = irow, column = jcol, value = top_left_cell_value)

 

@Дмитро Олександрович is almost right, but I had to change a few things to fix his answer: @Дмитро Олександрович 几乎是对的,但我不得不改变一些事情来修复他的答案:

  1. You'll have a AttributeError: 'MergedCell' object attribute 'value' is read-only error, because you need to unmerge merged cells before change their value.您将遇到AttributeError: 'MergedCell' object attribute 'value' is read-only错误,因为您需要在更改其值之前取消合并合并的单元格。 (see here: https://foss.heptapod.net/openpyxl/openpyxl/-/issues/1228 ) (见这里: https : //foss.heptapod.net/openpyxl/openpyxl/-/issues/1228

  2. you can not iterate directly over ws.merged_cells.ranges because iterating through a 'ranges' list object in python and changing it (with an unmerge_cells function or a pop function for example) will result in changing only half of the objects (see here: https://foss.heptapod.net/openpyxl/openpyxl/-/issues/1085 ).您不能直接在 ws.merged_cells.ranges 上迭代,因为在 python 中迭代“范围”列表对象并更改它(例如使用unmerge_cells函数或pop函数)将导致仅更改一半的对象(请参见此处: https://foss.heptapod.net/openpyxl/openpyxl/-/issues/1085 )。 You need to create a different list and iterate on it.您需要创建一个不同的列表并对其进行迭代。

The below code fromhttp://thequickblog.com/merge-unmerge-cells-openpyxl-in-python/ is worked for me.以下来自http://thequickblog.com/merge-unmerge-cells-openpyxl-in-python/ 的代码对我有用

import openpyxl 
from openpyxl.utils import range_boundaries
wbook=openpyxl.load_workbook("openpyxl_merge_unmerge.xlsx")
sheet=wbook["unmerge_sample"]
for cell_group in sheet.merged_cells.ranges:
    min_col, min_row, max_col, max_row = range_boundaries(str(cell_group))
    top_left_cell_value = sheet.cell(row=min_row, column=min_col).value
    sheet.unmerge_cells(str(cell_group))
    for row in sheet.iter_rows(min_col=min_col, min_row=min_row, max_col=max_col, max_row=max_row):
        for cell in row:
            cell.value = top_left_cell_value
wbook.save("openpyxl_merge_unmerge.xlsx")
exit()

I was getting errors and a deprecation warning until I did this:在我这样做之前,我收到了错误和弃用警告:

from openpyxl.utils import range_boundaries

for group in sheet.merged_cells.ranges: # merged_cell_ranges deprecated
    display(range_boundaries(group._get_range_string())) # expects a string instead of an object
    min_col, min_row, max_col, max_row = range_boundaries(group._get_range_string())
    top_left_cell_value = sheet.cell(row=min_row, column=min_col).value
    for row in sheet.iter_rows(min_col=min_col, min_row=min_row, max_col=max_col, max_row=max_row):
        for cell in row:
            cell.value = top_left_cell_value

Regarding @Charlie Clark's selected answer and the other answers which use the code from http://thequickblog.com/merge-unmerge-cells-openpyxl-in-python , you can unmerge the cells more easily without dealing with range_boundaries and those conversions.关于@Charlie Clark 的选定答案和其他使用http://thequickblog.com/merge-unmerge-cells-openpyxl-in-python代码的答案,您可以更轻松地取消合并单元格,而无需处理range_boundaries和这些转换。

In my case, I wanted to unmerge cells like this, copying the border, font, and alignment information as well:就我而言,我想像这样取消合并单元格,同时复制边框、字体和对齐信息:

                    +-------+------+
+-------+------+    | Date  | Time |
| Date  | Time |    +=======+======+
+=======+======+ -> | Aug 6 | 1:00 |
|       | 1:00 |    +-------+------+
| Aug 6 | 3:00 |    | Aug 6 | 3:00 |
|       | 6:00 |    +-------+------+
+-------+------+    | Aug 6 | 6:00 |
                    +-------+------+

For the current latest version of openpyxl==3.0.9 , I found that the following works best for me:对于当前最新版本的openpyxl==3.0.9 ,我发现以下最适合我:

from copy import copy

from openpyxl import load_workbook, Workbook
from openpyxl.cell import Cell
from openpyxl.worksheet.cell_range import CellRange
from openpyxl.worksheet.worksheet import Worksheet


def unmerge_and_fill_cells(worksheet: Worksheet) -> None:
    """
    Unmerges all merged cells in the given ``worksheet`` and copies the content
    and styling of the original cell to the newly unmerged cells.

    :param worksheet: The Excel worksheet containing the merged cells.
    """

    # Must convert iterator to list to eagerly evaluate all merged cell ranges
    # before looping over them - this prevents unintended side-effects of
    # certain cell ranges from being skipped since `worksheet.unmerge_cells()`
    # is destructive.
    all_merged_cell_ranges: list[CellRange] = list(
        worksheet.merged_cells.ranges
    )

    for merged_cell_range in all_merged_cell_ranges:
        merged_cell: Cell = merged_cell_range.start_cell
        worksheet.unmerge_cells(range_string=merged_cell_range.coord)

        # Don't need to convert iterator to list here since `merged_cell_range`
        # is cached
        for row_index, col_index in merged_cell_range.cells:
            cell: Cell = worksheet.cell(row=row_index, column=col_index)
            cell.value = merged_cell.value

            # (Optional) If you want to also copy the original cell styling to
            # the newly unmerged cells, you must use shallow `copy()` since
            # cell style properties are proxy objects which are not hashable.
            #
            # See <https://openpyxl.rtfd.io/en/stable/styles.html#copying-styles>
            cell.alignment = copy(merged_cell.alignment)
            cell.border = copy(merged_cell.border)
            cell.font = copy(merged_cell.font)


# Sample usage
if __name__ == "__main__":
    workbook: Workbook = load_workbook(
        filename="workbook_with_merged_cells.xlsx"
    )
    worksheet: Worksheet = workbook["My Sheet"]

    unmerge_and_fill_cells(worksheet=worksheet)
    workbook.save(filename="workbook_with_unmerged_cells.xlsx")

Here is a shorter version without comments and not copying styles:这是一个较短的版本,没有注释,也没有复制样式:

from openpyxl.worksheet.worksheet import Worksheet

def unmerge_and_fill_cells(worksheet: Worksheet) -> None:
    for merged_cell_range in list(worksheet.merged_cells.ranges):
        worksheet.unmerge_cells(range_string=merged_cell_range.start_cell)

        for row_col_indices in merged_cell_range.cells:
            worksheet.cell(*row_col_indices).value = merged_cell.value

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM