파이썬 openpyxl data_only=True가 None일 때 수식 캐시와 재계산 확인 순서

data_only=True에서 수식 결과가 None이면 곧바로 빈 셀이라고 단정하면 안 됩니다. 먼저 data_only=False로 수식 자체가 존재하는지 확인한 뒤, 저장된 계산 캐시가 없는 경우인지 오래된 값인지 구분하고 재계산 여부를 결정해야 합니다.

먼저 결론: None은 네 가지 경우를 나눠 봐야 합니다

openpyxl에서 data_only는 수식을 계산하는 스위치가 아니라 읽을 값을 선택하는 옵션입니다. data_only=False로 열면 수식 문자열을 확인하고, data_only=True로 열면 Excel이 마지막으로 저장한 수식 결과값을 확인하는 방식입니다.

따라서 수식 셀의 None은 다음을 구분한 뒤 해석해야 합니다.

  1. 셀에 애초에 수식이 없다.
  2. 수식은 있지만 저장된 계산 결과가 없다.
  3. 저장된 결과가 비어 있는 값을 나타낸다.
  4. 저장된 결과가 현재 입력과 맞지 않는 오래된 캐시다.

None 하나만 보고 이 중 하나를 선택할 수 없다는 점이 핵심입니다. 운영 배치에서 None을 곧바로 성공 또는 실패로 바꾸지 말고, 수식 존재 여부와 재계산 필요성을 별도 판단값으로 남기는 편이 안전합니다.

1. 같은 셀을 두 방식으로 읽어 수식 존재 여부 확인하기

먼저 원본 파일을 두 번 읽습니다. 첫 번째는 수식 자체를 보고, 두 번째는 저장된 결과값을 봅니다.

from pathlib import Path
from openpyxl import load_workbook

path = Path("report.xlsx")
sheet_name = "Summary"
cell_ref = "D8"

formula_wb = load_workbook(path, data_only=False, read_only=True)
value_wb = load_workbook(path, data_only=True, read_only=True)

formula = formula_wb[sheet_name][cell_ref].value
cached_value = value_wb[sheet_name][cell_ref].value

print({
    "cell": f"{sheet_name}!{cell_ref}",
    "formula": formula,
    "cached_value": cached_value,
})

formula가 =SUM(B8:C8)처럼 보이면 셀에는 수식이 있는 것입니다. 이때 cached_value가 None이어도 실제 수식이 없다고 결론 내리면 안 됩니다. 반대로 formula도 None이라면, 적어도 해당 셀에는 읽어 온 수식이 없으므로 수식 캐시 문제와는 다른 경로로 확인해야 합니다.

read_only=True는 읽기 작업의 메모리 사용을 줄이기 위한 선택일 뿐, 수식을 계산하게 만들지는 않습니다. 원본을 수정하거나 재계산 결과를 저장하는 코드는 이 확인 단계와 분리하는 것이 좋습니다.

2. data_only=True가 보여 주는 값의 의미

OOXML 파일에서는 수식과 저장된 값이 별도로 표현될 수 있습니다. openpyxl의 data_only=True는 이 구조를 이용해 수식 문자열 대신 마지막으로 저장된 결과값을 읽습니다. 즉, openpyxl이 Excel처럼 수식을 실행해서 현재 값을 만들어 주는 동작으로 이해하면 안 됩니다.

이 차이 때문에 다음과 같은 흐름이 가능합니다.

  • Python에서 수식을 넣고 파일을 저장한다.
  • 파일을 다시 data_only=True로 연다.
  • 수식은 data_only=False에서 보이지만, 저장된 결과는 None일 수 있다.

이 경우에는 수식의 문법이 맞는지와 계산 결과가 파일에 저장되어 있는지를 따로 봐야 합니다. data_only=True의 반환값만으로 수식의 계산 성공 여부를 판정하는 방식은 충분하지 않습니다.

3. 오래된 캐시인지 확인하는 방법

저장된 값이 None이 아니라 숫자나 문자열이라도 현재 입력을 반영한다는 보장은 없습니다. 저장된 값이 언제, 어떤 입력을 기준으로 계산되었는지 openpyxl 단독으로 판정하기는 어렵습니다.

운영에서 확인할 순서는 다음과 같습니다.

  1. 같은 셀을 data_only=False와 data_only=True로 읽어 수식과 저장값을 함께 기록합니다.
  2. 수식이 참조하는 입력 셀이 최근에 바뀌었는지 확인합니다.
  3. 보고서의 기준 입력과 저장된 결과가 일치하는지 독립적인 계산 또는 업무 규칙으로 검증합니다.
  4. 일치 여부를 판단할 수 없으면 캐시를 신뢰하지 않고 재계산 경로로 보냅니다.

여기서 중요한 것은 캐시가 있다는 사실과 캐시가 최신이라는 사실을 분리하는 것입니다. 숫자가 반환되었다는 이유만으로 배치 성공으로 처리하지 말고, 입력 변경 여부와 검증 결과를 함께 기록해야 합니다.

4. 재계산할지 결정하는 기준

재계산이 필요하다면 먼저 원본 파일을 보존합니다. 그 다음 Excel 또는 해당 파일을 처리할 수 있는 호환 계산 엔진에서 재계산하고, 계산된 파일을 별도 경로에서 다시 읽어 결과를 확인하는 흐름을 선택할 수 있습니다.

Excel은 의존성 트리와 계산 체인을 바탕으로 수식을 계산하며, 일반 계산·전체 계산·강제 전체 계산 같은 재계산 동작을 제공합니다. 어떤 동작을 사용할지는 파일의 변경 범위와 운영 환경에 달려 있으므로, 모든 파일에 같은 재계산 방식을 적용한다고 단정하기는 어렵습니다.

재계산을 선택할 때는 다음처럼 판단하면 됩니다.

  • 입력이 바뀌었고 결과의 최신성이 중요하면 재계산 후 검증합니다.
  • 수식 존재 여부만 확인하면 되면 data_only=False 확인으로 충분할 수 있습니다.
  • 외부 계산 엔진을 사용할 수 없으면 저장된 캐시를 현재 결과로 간주하지 않고, 독립 계산이 가능한 범위만 검증합니다.
  • 원본을 덮어써야 하는지 확실하지 않으면 재계산 산출물을 별도 파일로 만듭니다.

배치에 넣을 최소 판정 코드

다음 함수는 수식과 저장된 값을 함께 읽어 후속 판단에 필요한 상태를 나눕니다. 이 함수는 수식을 계산하지 않으며, needs_recalculation은 캐시가 없을 때 재계산 경로를 검토하라는 신호입니다.

from pathlib import Path
from openpyxl import load_workbook


def inspect_formula_cell(path: str, sheet_name: str, cell_ref: str) -> dict:
    source = Path(path)

    formula_wb = load_workbook(source, data_only=False, read_only=True)
    value_wb = load_workbook(source, data_only=True, read_only=True)

    formula = formula_wb[sheet_name][cell_ref].value
    cached_value = value_wb[sheet_name][cell_ref].value

    has_formula = isinstance(formula, str) and formula.startswith("=")
    return {
        "has_formula": has_formula,
        "formula": formula,
        "cached_value": cached_value,
        "needs_recalculation": has_formula and cached_value is None,
    }

이 결과의 needs_recalculation이 True라고 해서 재계산이 반드시 성공한다는 뜻은 아닙니다. 계산 엔진을 선택하고, 원본 보존 여부를 정하고, 재계산 뒤 결과를 다시 검증하는 운영 단계가 이어져야 합니다. 특히 수식이 참조하는 범위나 외부 연결에 따라 별도 확인이 필요할 수 있습니다.

정리: None을 읽었을 때의 판단 순서

openpyxl data_only=True None 문제는 값을 읽는 옵션과 값을 계산하는 작업을 혼동할 때 생깁니다. 수식 결과가 None이면 다음 순서를 지키면 됩니다.

  1. data_only=False로 수식 존재 여부를 확인합니다.
  2. 수식이 있으면 data_only=True의 값은 저장된 캐시로 해석합니다.
  3. 캐시가 없거나 최신성 근거가 없으면 재계산 대상으로 분류합니다.
  4. 원본을 보존한 뒤 호환 계산 엔진에서 재계산하고 결과를 다시 검증합니다.
  5. 계산하지 못한 값은 성공으로 포장하지 않고, 배치 로그에 판단 근거를 남깁니다.

다음으로는 수식과 계산값을 함께 검증하는 방법, Excel 재계산 없이 독립 계산으로 확인할 수 있는 범위, pandas.read_excel()에서 수식 열이 NaN으로 보이는 이유를 이어서 살펴볼 수 있습니다.

참고 자료

Leave a Comment