파이썬 openpyxl MergedCell read-only 오류 해결: 병합 셀 좌상단 확인법

엑셀 템플릿에 값을 넣다가 MergedCell의 value가 read-only라는 오류가 나면, 먼저 쓰려는 좌표가 병합 범위의 좌상단인지 확인해야 합니다. 병합 영역의 값은 좌상단 셀에만 쓰는 것이 기본이며, 여러 칸에 각각 데이터를 넣어야 할 때만 병합을 해제하는 쪽을 검토합니다.

오류가 의미하는 것부터 확인하기

openpyxl의 병합 셀은 화면에서 하나의 큰 셀처럼 보이지만, 내부적으로는 좌상단 셀과 나머지 영역이 다르게 취급됩니다. 병합 범위의 좌상단은 실제 값을 보관하는 일반 셀이고, 나머지 좌표는 MergedCell로 다뤄져 값을 직접 바꿀 수 없습니다.

따라서 ws["C3"].value = "부서" 같은 코드에서 C3가 A1:D3 병합 범위의 일부라면, C3가 좌상단이 아닌지부터 봐야 합니다. 이때 병합을 무조건 풀기보다 템플릿이 한 문구를 표시하기 위한 것인지, 각 칸에 별도 데이터를 배치하기 위한 것인지 구분하는 것이 안전합니다.

오류 좌표가 병합 범위의 앵커인지 찾는 방법

ws.merged_cells.ranges를 출력하면 현재 워크시트의 병합 범위를 확인할 수 있습니다. 범위의 min_row와 min_col이 좌상단 좌표에 해당합니다.

from openpyxl import load_workbook

wb = load_workbook("report_template.xlsx")
ws = wb["보고서"]

target = "C3"

for merged_range in ws.merged_cells.ranges:
    if target in merged_range:
        anchor = ws.cell(
            row=merged_range.min_row,
            column=merged_range.min_col,
        )
        print(f"{target} -> {merged_range}, 앵커: {anchor.coordinate}")
        break
else:
    print(f"{target}는 병합 범위에 없습니다.")

예를 들어 출력된 범위가 A1:D3이고 앵커가 A1이라면, 제목 하나를 표시하려는 경우에는 다음처럼 좌상단에 값을 씁니다.

ws["A1"] = "월간 운영 보고서"
wb.save("report_filled.xlsx")

대상 좌표가 병합 범위에 속하지 않는다면 MergedCell이 원인이 아닐 수 있습니다. 특히 워크북을 read_only=True로 열었는지 함께 확인해야 합니다. read-only 모드의 워크시트는 읽기 전용이고, 반환되는 셀도 일반 Cell이 아닌 ReadOnlyCell입니다.

병합을 유지해야 하는 경우

다음처럼 화면에 하나의 제목, 부서명, 기간을 보여주는 영역이라면 병합을 유지하고 앵커 셀만 수정하는 편이 맞습니다.

  • 병합된 영역에 표시할 값이 하나일 때
  • 템플릿의 정렬·테두리·채우기 같은 시각적 구성을 유지해야 할 때
  • 후속 처리도 병합 범위 전체가 하나의 의미를 가질 때

이 경우 코드가 항상 앵커를 찾도록 작은 함수를 두면 좌표를 잘못 지정하는 실수를 줄일 수 있습니다.

def merged_anchor(ws, coordinate):
    for merged_range in ws.merged_cells.ranges:
        if coordinate in merged_range:
            return ws.cell(merged_range.min_row, merged_range.min_col)
    return ws[coordinate]

cell = merged_anchor(ws, "C3")
cell.value = "월간 운영 보고서"

다만 병합 범위 안에서 C3에 값을 넣는다는 입력 의미 자체가 필요하다면, 단순히 좌상단으로 바꾸는 것이 데이터 모델을 바꿔도 되는지 먼저 확인해야 합니다. 앵커에 쓰면 화면에는 보통 원하는 문구가 보이지만, C3라는 위치에 별도 값을 저장하는 것은 아닙니다.

병합을 해제할지 판단하는 기준

한 병합 영역 안에 여러 값을 각각 저장해야 한다면 병합을 유지한 채 우회하기보다 데이터 배치를 먼저 다시 설계해야 합니다. 그때 unmerge_cells()를 검토할 수 있습니다.

merged_ref = "A1:D3"
ws.unmerge_cells(merged_ref)

ws["A1"] = "부서"
ws["B1"] = "개발"
ws["C1"] = "기간"
ws["D1"] = "2026-11"

병합 해제는 단순한 오류 회피가 아닙니다. 기존에 병합 영역 하나로 표현하던 레이아웃을 여러 셀의 구조로 바꾸는 작업이므로 다음을 확인한 뒤 적용해야 합니다.

  1. 병합 전 좌상단에 있던 원래 값이 무엇인지 보존할지 결정합니다.
  2. 해제 뒤 각 셀에 어떤 데이터가 들어갈지 정합니다.
  3. 병합으로 보이던 테두리와 정렬이 필요한지 확인합니다.
  4. 저장한 파일을 다시 열어 값, 병합 목록, 화면용 서식을 점검합니다.

병합을 해제한 뒤 다시 같은 영역을 합치면, 여러 값을 보존하는 구조와는 양립하지 않을 수 있습니다. 여러 값을 사람이 읽기 위한 표로 보여주려는 목적이라면 병합 대신 일반 셀, 표 머리글 또는 별도 열을 쓰는 편이 더 예측 가능합니다.

수정 전에 함께 확인할 것

오류 메시지만 보고 병합 해제를 먼저 실행하면 원래 템플릿의 의도를 잃을 수 있습니다. 운영에서는 다음 순서가 실용적입니다.

  • 오류가 난 좌표를 기록합니다.
  • ws.merged_cells.ranges에서 해당 좌표가 포함된 범위를 찾습니다.
  • 범위의 좌상단에 쓸 하나의 값인지, 여러 셀에 나눌 데이터인지 결정합니다.
  • 워크북을 read_only=True로 열지 않았는지 확인합니다.
  • 수정 후 저장한 파일을 다시 열어 병합 범위와 핵심 값을 검증합니다.

결론적으로 MergedCell read-only 오류의 첫 해결책은 병합을 전부 해제하는 것이 아닙니다. 하나의 값을 표시하는 템플릿이면 병합 범위의 좌상단 앵커에 쓰고, 여러 값을 독립적으로 배치해야 할 때만 기존 값과 서식을 확인한 뒤 병합 해제를 선택하면 됩니다.

참고 자료

Leave a Comment