Watch at Ease세상을 읽고, 일상의 답을 찾다.

기술 · 실용 가이드

Python CSV에서 None과 빈 문자열이 같아지나요? 실행 버전과 교환 규칙 확인

인용 옵션이 있어도 읽기 동작까지 지원한다는 뜻은 아니다. 로컬 검증 20개로 파싱 문제, 되돌릴 수 없는 내보내기 손실, 문자열 또는 None만 다루는 대안을 구분한다.

내보내기와 3.12 읽기에서 결측값과 빈 텍스트 구분이 사라지는 경계의 오리지널 설명 일러스트.
오리지널 설명 일러스트: 원본 필드를 보존하고 쓰기·읽기를 따로 확인한다. 단일 None 행에는 쓰기 예외도 있다. Watch at Ease

CPython3.12.14에서 csv.QUOTE_NOTNULL로 [None, ""]를 쓰면 따옴표 없는 빈 필드와 따옴표 있는 빈 필드가 만들어졌다. 같은 옵션으로 읽으면 둘 다 빈 문자열이 됐다. 상수가 존재해도 배포된 리더가 이 차이를 보존하는 것은 아니다.

먼저 원본 바이트와 생성자가 정한 결측값 규칙을 확인한다. 이전 내보내기에서 결측값과 의도적인 빈 문자열을 같은 형태로 기록했다면 읽기 옵션을 바꿔도 원래 의미를 복원할 수 없다. 구분이 필요하면 파일을 보관하고 원본 데이터에서 다시 내보내야 한다.

1. 정보가 사라진 경계 찾기

원본 파일을 변경하지 않는다. 민감하지 않은 작은 표본을 repr()로 확인해 쉼표, 따옴표, 레코드 끝을 살핀다. 기본 csv.writer 왕복에서는 [None, ""]가 ["", ""]가 되어 쓰는 단계에서 정보가 사라졌다. NOTNULL 작성기는 서로 다른 필드를 남겼지만 3.12 리더가 합쳤다. 후자는 파서 호환성 경계다.

읽은 빈 문자열을 모두 None으로 바꾸는 것은 복구가 아니다. 두 필드 표본은 모두 None이 되어 의도적인 빈 값이 사라졌다. 이스케이프 규칙 없는 대체 표지도 충돌한다. None을 <NULL>로 바꾸면 실제 문자열 <NULL>과 구분되지 않는다.

2. 읽기와 쓰기, 한 열뿐인 행도 검증

sys.version을 출력하고 실제 운영 옵션으로 None, 빈 문자열, "001"이 든 표본을 쓰고 읽는다. Python3.12 문서는 QUOTE_NOTNULL과 QUOTE_STRINGS가 리더 동작에 영향을 주지 않는 버그를 명시한다. 관련 읽기 변경은 3.13에 문서화돼 있다. 실제 실행한 버전은 3.12.14뿐이다.

None 하나만 든 행도 시험한다. 실측 QUOTE_NOTNULL 작성기는 csv.Error: single empty field record must be quoted를 발생시켰다. 빈 문자열 하나인 행은 [""]로, 빈 레코드는 []로 읽혔다. 따옴표로 결측을 구분하는 규칙에서 오류만 피하려고 null에 따옴표를 붙이면 빈 텍스트를 기록하게 된다.

3. 변환 전에 수신 규칙 합의

지원하는 최신 리더의 문서상 QUOTE_NOTNULL 규칙은 따옴표 없는 빈 필드를 None으로 읽고 나머지를 문자열로 유지한다. 정수나 임의의 Python 객체를 복원하지 않는다. QUOTE_STRINGS에는 별도의 숫자 변환 규칙이 있으며 일반 객체 직렬화기가 아니다. 도입 전 양쪽 실제 환경에서 지원할 모든 행 형태를 검증한다.

PostgreSQL COPY CSV는 수신 규칙의 중요성을 보여준다. 기본적으로 따옴표 없는 빈 필드는 NULL, 따옴표 있는 빈 필드는 빈 텍스트다. FORCE_NULL과 FORCE_NOT_NULL로 해석이 바뀔 수 있다. 이는 PostgreSQL 문서의 동작이지 모든 CSV의 보장이 아니다. 데이터베이스나 Python3.13 통합 시험은 실행하지 않았다.

4. 양쪽을 제어할 때만 쓰는 제한된 대안

구형 환경에서 str 또는 None만 교환한다면 검증한 응용 계층은 None을 리터럴 필드 N으로, 모든 문자열을 S와 원문 전체의 결합으로 인코딩한다. 빈 텍스트는 S, 문자열 N은 SN, 실제 문자열 <NULL>은 S<NULL>이 된다. 이를 일반 csv.writer에 전달하고 쉼표, 따옴표, 내부 줄바꿈은 CSV 계층에 맡긴다.

디코딩할 때 정확히 N인 필드만 None으로 바꾼다. S로 시작하면 첫 글자만 제거하고 나머지 필드는 잘못된 태그로 거부한다. 쓰기 전에 문자열도 None도 아닌 값은 거부한다. 이는 명시적으로 합의해야 하는 새 응용 형식이다. 기존 일반 CSV에 무조건 적용하거나 COPY, pandas, 스프레드시트가 이 태그를 이해한다고 가정하지 않는다.

5. 값과 타입, 행 형태가 일치해야 완료

20개 단언 검증은 두 정보 손실 경계, 단일 null 행의 쓰기 예외, 빈 레코드와 빈 필드, 빈 값 변환과 표지 충돌, 13개 값의 태그 왕복을 다뤘다. None, 빈 텍스트, "001", "0", null이나 태그처럼 보이는 문자열, 쉼표, 따옴표, 내부 줄바꿈, Unicode를 포함했으며 값과 타입 모두 일치했다.

단일 null, 단일 빈 문자열, null 두 개의 행도 검증했고 정수·불리언, 잘못된 태그와 인코딩하지 않은 빈 필드는 거부했다. 이는 CPython3.12.14/Linux에서 명시한 문자열 또는 None 규칙만 입증한다. 다른 버전이나 하위 소프트웨어를 인증하지 않는다. 각 경계에 대표 표본을 남기고 원래 구분이 사라졌다면 추측하지 말고 다시 내보낸다.

None, 빈 텍스트, 실제 태그 문자열을 N/S로 인코딩하는 오리지널 설명 일러스트.
오리지널 설명 일러스트: 합의한 str 또는 None 교환에만 사용하고 잘못된 태그는 거부한다. Watch at Ease