API나 DB 응답을 CSV로 저장하다가 ValueError: dict contains fields not in fieldnames가 발생했다면, 먼저 fieldnames와 실제 입력 딕셔너리의 키 차이를 확인해야 합니다. 원인이 새로 들어온 필드라면 열 목록을 확장할 수 있지만, 출력 계약에 없는 필드라면 명시적으로 제외해야 하며 extrasaction='ignore'는 데이터가 사라져도 되는 경우에만 제한적으로 사용해야 합니다. 이 판단 순서를 지키면 오류만 없애고 중요한 데이터를 놓치는 일을 줄일 수 있습니다.
이 글은 Python 공식 csv 문서의 DictWriter 동작을 기준으로, fieldnames 누락과 입력 구조 변경을 구분하고 CSV 출력을 계속할지 중단할지 결정하는 방법을 설명합니다.
오류의 의미: 출력 열과 입력 키가 어긋났다는 신호
DictWriter의 fieldnames는 CSV로 쓸 열의 이름과 순서를 정합니다. writerow()에 전달한 딕셔너리에 그 목록에 없는 키가 포함되면 기본 동작인 extrasaction='raise'에서 ValueError가 발생합니다.
예를 들어 API 응답에 status가 새로 추가됐는데 출력 열을 id, name, email로만 고정해 두었다면, 프로그램은 status를 어디에 기록해야 할지 알 수 없습니다. 이때 예외는 단순한 문법 오류라기보다 입력 데이터 계약과 CSV 출력 계약이 달라졌다는 신호에 가깝습니다.
반대로 입력 행에 fieldnames에 있는 키가 빠진 경우에는 restval이 사용됩니다. 따라서 “없는 키”라는 말이 출력 목록에 없는 입력 키인지, 입력 행에 빠진 출력 키인지 먼저 나눠 봐야 합니다.
1단계: 실제 키 차이를 먼저 기록합니다
오류가 난 행을 바로 무시하거나 fieldnames에 무조건 추가하지 말고, 예상 열과 실제 키를 비교합니다.
아래 예제의 row를 이어지는 출력 예제에서도 사용합니다.
import csv
fieldnames = ["id", "name", "email"]
row = {
"id": 101,
"name": "Kim",
"email": "kim@example.com",
"status": "active",
}
expected = set(fieldnames)
actual = set(row)
extra_keys = actual - expected
missing_keys = expected - actual
print("출력 목록에 없는 입력 키:", sorted(extra_keys))
print("입력 행에 없는 출력 키:", sorted(missing_keys))
여기서 extra_keys가 비어 있지 않으면 fieldnames에 없는 입력 필드가 있다는 뜻입니다. missing_keys는 반대 방향의 차이입니다. 이 비교 결과와 원본 응답의 구조를 로그로 남겨 두면, 일시적인 데이터 이상인지 지속적인 스키마 변경인지 구분하기 쉬워집니다.
2단계: 새 필드가 출력에 필요한지 판단합니다
추가 키가 보고서나 후속 분석에 필요한 정보라면 fieldnames에 열을 추가합니다. writeheader()는 생성자에 지정한 fieldnames를 헤더로 기록하므로, 열 목록을 바꿀 때는 헤더와 행이 같은 목록을 사용해야 합니다.
fieldnames = ["id", "name", "email", "status"]
with open("users.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()
writer.writerow(row)
저장한 CSV를 다시 열어 헤더와 행에 필요한 값이 들어 있는지 확인하고, CSV 소비자가 새 열을 받아들일 수 있는지도 살펴보세요. 이 선택은 기존 파일과의 호환성을 깨뜨리지 않는지 확인한 뒤 해야 합니다. 단순히 오류를 없애는 것이 목적이라면 열을 늘리는 것이 오히려 다른 시스템의 입력 형식을 바꿀 수 있습니다.
3단계: 필요 없는 필드는 명시적으로 제외합니다
입력에는 있지만 CSV 출력에는 필요하지 않은 필드라면, 출력 계약을 먼저 정하고 행을 변환하는 편이 분명합니다.
fieldnames = ["id", "name", "email"]
csv_row = {key: row[key] for key in fieldnames if key in row}
with open("users.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()
writer.writerow(csv_row)
이 방식은 출력할 필드를 허용 목록으로 코드에 드러냅니다. 특히 API 응답 전체를 그대로 저장하는 자동화에서는 개인 정보, 내부 상태값, 임시 메타데이터처럼 CSV에 넣지 않을 필드를 명시적으로 구분하는 것이 안전합니다. 제외가 의도된 것인지 확인할 수 있도록 extra_keys도 운영 로그에 남기는 것이 좋습니다. 다만 로그에는 민감한 값 대신 필요한 키 이름만 남길지 먼저 정하세요.
4단계: extrasaction=’ignore’는 손실을 허용할 때만 씁니다
extrasaction='ignore'를 지정하면 fieldnames에 없는 입력 값은 CSV 행에 기록되지 않습니다.
fieldnames = ["id", "name", "email"]
with open("users.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(
file,
fieldnames=fieldnames,
extrasaction="ignore",
)
writer.writeheader()
writer.writerow(row)
이 설정은 로그성 출력처럼 추가 필드가 중요하지 않고, 입력에 필드가 늘어나도 기존 CSV 형식을 유지해야 할 때 선택할 수 있습니다. 그러나 새 필드가 분석이나 정산에 필요한지 모르는 상태에서 적용하면 오류는 사라져도 정보가 조용히 누락됩니다. 따라서 적용 전에는 무시할 필드의 의미와 누락 허용 여부를 확인하고, 실제로 무시된 키를 기록하는 편이 좋습니다.
작업을 계속할지 중단할지의 기준
운영에서는 다음 순서로 결정하면 됩니다.
- 실제
extra_keys와missing_keys를 기록합니다. - 추가 키가 출력에 필요한 정보인지 확인합니다.
- 필요하면
fieldnames와 소비자 계약을 함께 변경합니다. - 필요 없으면 명시적으로 제외하고, 제외 사실을 남깁니다.
- 기존 열을 유지해야 하면서 추가 필드가 비핵심일 때만
ignore를 검토합니다. - 의미를 판단할 수 없거나 필수 필드가 바뀌었다면 CSV 생성을 중단하고 스키마 변경을 확인합니다.
특히 새 필드가 생겼다는 이유만으로 ignore를 기본값처럼 적용하면 데이터 변경을 감지하지 못할 수 있습니다. 반대로 모든 차이를 장애로 취급하면 정상적인 확장 필드 때문에 작업이 불필요하게 멈출 수 있으므로, 필드별 중요도와 CSV 소비자의 계약을 기준으로 판단해야 합니다.
마무리: 오류 수정이 아니라 데이터 계약 확인입니다
DictWriter의 fieldnames 오류는 extrasaction 한 줄로 숨길 수 있는 문제가 아니라, 입력 데이터와 출력 CSV의 계약을 맞추라는 신호입니다. 먼저 키 차이를 확인하고, 필요한 열은 추가하며, 필요 없는 열은 명시적으로 제외하십시오. extrasaction='ignore'는 손실을 설명할 수 있을 때만 사용하고, 구조 변경의 의미가 불분명하면 작업을 보류하는 편이 안전합니다.
다음으로는 CSV 열을 고정 스키마로 관리하는 방법과 API 응답 구조 변경을 자동화에서 감지하는 로그 항목을 정해 두면 좋습니다. JSON 응답 자체의 구조 검증이 필요하다면 파이썬 AI JSON 응답 오류를 줄이는 검증 방법도 이어서 살펴볼 수 있습니다.
출처: Python Software Foundation, csv — CSV File Reading and Writing