큰 CSV를 일정한 행 수로 나누고 원본 ID와 내용을 검산하기
P16_Practice_CSV_Chunks.zip

1. 준비
ZIP을 새 빈 로컬 연습 폴더에 직접 풀고 README.txt가 있는 폴더에서 실행합니다.
이 예제는 CPython 3.12.14에서 검수했으며 표준 라이브러리만 사용합니다. 추가 설치·인터넷·API 키 불필요.
실제 검산: CPython 3.12.14 / Linux. 다른 Python 버전·Windows·Microsoft Excel 실행 시험은 하지 않았습니다.
다른 버전은 사용 전에 제공된 전체 tests, 특히 메모의 CR·CRLF 원문 보존 시험을 모두 통과해야 합니다.
다른 버전의 호환성을 검증한 자료는 아닙니다.
공식 3.12 문서의 현재 표제 3.12.15와 실제 실행 버전은 다릅니다.
실제 업무·네트워크·동기화·공유 폴더 대신 개인 로컬 연습 사본을 사용합니다.
다른 프로그램이 입력·출력을 바꾸지 않게 하고 한 번에 한 작업만 실행합니다.

2. 동일한 입력
input/records.csv: 가상 문구류 10건, 고유 ID 문자열 1~10.
ID 2의 memo는 '파랑, 검정', ID 3의 memo는 '첫 줄'과 '둘째 줄' 사이 실제 LF 줄바꿈,
ID 4의 memo는 큰따옴표를 포함한 '"확인" 표시', ID 6의 memo는 빈 문자열입니다.
물리 줄 수로 자르지 않고 csv.reader의 논리 레코드 한 건씩 처리합니다.
헤더는 id,item,memo 세 열과 정확한 순서를 요구합니다. 인코딩은 BOM 없는 UTF-8.
각 레코드 정확히 3필드, id는 [1-9][0-9]* 형식으로 앞자리 0 없는 양의 십진수 문자열.
모든 ID 고유, item 빈 문자열 거부, memo 빈 문자열 허용. 공백·줄바꿈·값 치환 없음.
필드 하나의 파서 한도는 1,000,000자입니다. 출력 바이트 크기 제한은 아닙니다.
UTF-8 오류·열 수 오류·중복 ID·반복 헤더·파서가 감지한 인용 오류는 중단합니다.
csv strict=True는 모든 CSV 방언의 독립 문법 검증기가 아닙니다.
실제 자료 스키마가 다르면 원본 ID를 바꾸지 말고 규칙과 테스트를 별도로 설계하세요.

3. 정상 실행
python --version
python -m unittest discover -s tests -v
python split_csv.py split --source input/records.csv --output chunks4 --max-rows 4
python split_csv.py verify --source input/records.csv --output chunks4 --max-rows 4

python이 검수한 CPython 3.12.14인지 확인합니다. 먼저 전체 tests를 실행하고 하나라도 실패하면 분할 명령을 진행하지 않습니다. 시스템 명령이 python3이면 첫 단어만 python3으로 바꿉니다.
chunks4는 존재하지 않는 새 이름이어야 합니다. 그 상위 폴더는 이미 있는 일반 폴더여야 합니다.
출력 경로의 기존 폴더·파일·정상/끊어진 링크를 모두 거부합니다.
입력과 출력이 같은 이름, 입력 파일 아래 출력 경로, 입력 또는 출력 상위 경로의 링크/reparse 표식도 거부합니다.
기존 출력은 지우거나 덮어쓰지 않습니다. 재실행은 --output chunks4_again처럼 새 이름을 사용합니다.

예상 결과:
chunks4/chunk_000001.csv: 헤더 1 + 데이터 4건, ID 1·2·3·4
chunks4/chunk_000002.csv: 헤더 1 + 데이터 4건, ID 5·6·7·8
chunks4/chunk_000003.csv: 헤더 1 + 데이터 2건, ID 9·10
chunks4/summary.json: rows 10, chunks 3, rows_per_chunk [4,4,2], headers_per_chunk [1,1,1], duplicates 0,
                     id_sequence_equal true, field_values_equal true
chunk_000004.csv는 생기지 않습니다. 헤더는 --max-rows의 데이터 수에 포함하지 않습니다.
같은 입력에 대해 콘솔 JSON은 expected/normal_summary.json과 동일합니다.
원본 records.csv의 SHA-256:
961a45aa41dbead9203a56b1e144c5b17623fc81b742fdae351c2fb85c6d01e3
다른 파일에는 다른 해시가 나옵니다. 제공 입력을 다시 저장하면 줄바꿈 바이트 등에 따라 달라질 수 있습니다.

검산은 각 파일의 헤더를 확인하고 번호순으로 데이터만 연결해 원본과 순서·ID·세 필드 전체를 비교합니다.
누락·추가·중복·순서 변경·메모 변경·번호 누락·빈 꼬리 파일·중간의 덜 찬 파일을 거부합니다.
완성 폴더의 summary.json도 있어야 하며 현재 검산 결과와 일치해야 합니다.
원본 바이트는 그대로 두지만 출력은 UTF-8과 LF 레코드 줄바꿈으로 다시 씁니다.
따옴표 인용 방식·레코드 줄바꿈 바이트까지 원본과 같은 분할은 아닙니다. 필드 내용은 보존합니다.
CSV의 메모 안 줄바꿈·쉼표·따옴표와 수식처럼 보이는 문자열을 치환하지 않습니다.
결과는 먼저 텍스트 편집기로 확인하세요. 출처를 모르는 CSV를 Excel에서 더블클릭해 직접 열지 마세요.

4. 경계 입력 실행
python split_csv.py split --source input/records.csv --output chunks1 --max-rows 1
python split_csv.py split --source input/records.csv --output chunks10 --max-rows 10
python split_csv.py split --source input/eight_records.csv --output chunks8 --max-rows 4
python split_csv.py split --source input/header_only.csv --output chunks0 --max-rows 4

chunks1: [1,1,1,1,1,1,1,1,1,1] 데이터 10개 CSV
chunks10: [10] 데이터 1개 CSV, 끝의 빈 파일 없음
chunks8: [4,4] 데이터 2개 CSV, 끝의 빈 파일 없음
chunks0: CSV 0개 + summary.json. 올바른 헤더만 있는 입력은 허용합니다.
완전히 빈 파일은 헤더도 없으므로 거부합니다.
expected/boundary_expectations.json에 기대값이 있습니다.

5. 실패·복구
python split_csv.py split --source input/failures/late_unclosed_quote.csv --output bad_chunks --max-rows 4

앞의 8건 뒤에 닫히지 않은 인용문이 있어 ERROR: unexpected end of data, 종료 코드 2가 나옵니다.
처리 가능한 이 오류에서는 임시 chunks를 정리하며 bad_chunks 완성 경로는 생기지 않습니다.
원본 파일은 쓰기 모드로 열지 않습니다. 제공 입력 19개 시나리오에서 실행 전후 해시가 같았습니다.
input/failures/의 14개 CSV는 모두 오류를 보여 주기 위한 가상 반례입니다.
empty.csv, wrong_header.csv, short_row.csv, extra_row.csv, unclosed_quote.csv, after_quote.csv,
invalid_utf8.csv, bom.csv, blank_record.csv, empty_item.csv, noncanonical_id.csv,
late_unclosed_quote.csv, duplicate_id.csv, repeated_header.csv.
invalid_utf8.csv는 의도적으로 잘못된 UTF-8 바이트를 넣었으므로 편집기 경고가 날 수 있습니다.
0·음수 --max-rows는 ERROR로 거부합니다. 1.5 같은 비정수는 argparse가 거부하고 종료 코드 2입니다.

기존 출력: 지우지 말고 새 이름으로 재실행합니다. 재검산만 하려면 verify 명령을 사용합니다.
헤더·너비 오류: 열 이름·순서를 원본 근거와 비교합니다. 누락 값을 자동 채우거나 잘못된 행을 버리지 않습니다.
중복 ID: 파일을 넘어 같은 ID가 있으면 중단합니다. 원본 ID를 임의로 바꾸지 마세요.
인코딩·인용 오류: 원본 근거를 확인한 새 사본에서 수정합니다. 오류 무시 옵션은 없습니다.
검산 실패: 결과를 전달하지 말고 새 결과를 만들어 대조합니다. 보고서 숫자만 수정하지 마세요.

숨김 임시폴더 .출력이름.incomplete-...에 쓰고 재읽기 검산 후 summary.json을 기록한 뒤
새 출력 폴더 이름으로 바꿉니다. 시작 전과 확정 직전에 기존 경로를 검사합니다.
이 검사는 경쟁 생성·동시 변경을 막는 잠금이 아닙니다. POSIX rename은 기존 대상을 대체할 수 있습니다.
단일 로컬 작업 가정입니다. 동기화 폴더·네트워크 드라이브·동시 실행에 그대로 적용하지 않습니다.
처리 가능한 파싱/검증 오류의 정리는 시험했지만 강제 종료·전원 장애·권한 변경·장치 장애 복구는 보장하지 않습니다.
강제 종료 후 임시폴더가 남을 수 있습니다. 이를 완성 이름으로 바꾸지 말고 보존한 채 새 연습 폴더에서 다시 시작합니다.
정리 권한을 잃어 임시폴더가 남은 경우도 성공 결과로 사용하지 않습니다.
Windows junction/reparse 판정 분기는 코드에 있지만 실제 Windows 실행은 하지 않았습니다.

6. 메모리와 적용 범위
전체 데이터 행은 목록으로 읽지 않고 한 레코드씩 처리합니다.
ID 중복 검사 seen 집합은 O(N), 결과 파일 목록·행 수 목록은 O(파일 개수) 메모리를 사용합니다.
필드/레코드 내용 크기에 따른 버퍼도 필요합니다. 전체 프로그램의 일정한 메모리 사용을 보장하지 않습니다.
수천만 ID용 메모리 제한 처리는 로컬 데이터베이스/디스크 정렬 등 별도 설계가 필요합니다.
분할 후 재읽기와 해시 검사를 위해 파일을 여러 번 읽습니다. 대용량 성능 측정은 하지 않았습니다.
그룹별 분할·통계 집계·바이트 용량 기준 분할·오류 자동 수정·병렬 처리 기능은 없습니다.

7. 재현 검사
python -m unittest discover -s tests -v
python tests/run_fixture_checks.py

자동시험 48개. 메모 안의 단독 CR 및 CRLF 값 보존을 포함합니다. 기존 경로·원본 보존·재실행·결과 변조·링크는 격리 임시 폴더에서 만듭니다.
별도 fixture 검사 19개 시나리오. input에 제공된 파일을 실제로 읽고 모든 입력 해시 불변을 확인합니다.
유닉스 심볼릭 링크를 만들 수 있는 Linux 로컬 시험 결과입니다. Windows 실행 확인으로 해석하지 마세요.
정상 검사는 종료 코드 0, 의도된 오류 명령은 종료 코드 2가 기대값입니다.

8. 파일 구성
split_csv.py                본문과 동일한 전체 코드
tests/test_split_csv.py      격리 자동시험 48개
tests/run_fixture_checks.py  제공 CSV 19개 시나리오 검사
input/records.csv            가상 10건
input/eight_records.csv      가상 8건
input/header_only.csv        올바른 헤더만 있는 입력
input/failures/              잘못된 입력 14개
expected/chunks4/            독립 구성한 세 CSV와 고정 summary 비교 기준
expected/normal_summary.json 고정 정상 JSON
expected/boundary_expectations.json 경계·실패 기대값
actual/chunks4/              실제 실행에서 보관한 세 CSV와 summary
logs/                       테스트 전 실패/구현 후 통과/fixture·실제 명령 기록/런타임 버전
verification.json           실제 검산 범위와 한계
SHA256SUMS.json              본 목록 자체를 제외한 배포 파일의 SHA-256·바이트 수
README.txt                  이 안내

ZIP 최상위에는 실행 후 생성할 chunks4를 넣지 않았습니다.
expected와 actual은 비교용이며 수정하지 않습니다. 새로 만든 chunks4와 비교합니다.
실제 기록은 Python 3.12.14 / Linux의 가상 실습 결과만 증명합니다.

9. 공식 자료
확인일 2026-10-05, Python Software Foundation 3.12 계열 문서 현재 표제 3.12.15
https://docs.python.org/3.12/library/csv.html
https://docs.python.org/3.12/library/itertools.html#itertools.zip_longest
https://docs.python.org/3.12/library/tempfile.html#tempfile.TemporaryDirectory
https://docs.python.org/3.12/library/pathlib.html#pathlib.Path.lstat
https://docs.python.org/3.12/library/pathlib.html#pathlib.Path.rename
