파이썬123번7 MIN
텍스트 vs 이진 모드: 개행 문자와 인코딩의 차이
Python 파일 입출력에서 텍스트 모드와 이진 모드의 차이를 정확히 설명합니다. 개행 문자 자동 변환, str vs bytes, newline 매개변수, 이진 파일 손상 방지까지 다룹니다.
이 글은 PALDYN Tech Blog의 글입니다.원문 보기
지난 글에서 with 문으로 파일을 안전하게 열고 닫는 방법을 살펴봤다. open()을 쓸 때 모드로 'r'과 'rb'를 구별하는 것은 단순히 str을 반환하느냐 bytes를 반환하느냐의 차이가 아니다. 내부적으로 개행 문자 변환과 인코딩/디코딩 레이어가 있느냐 없느냐의 차이이며, 이것이 잘못 선택되면 이미지나 ZIP 파일이 조용히 손상된다.
텍스트 모드와 이진 모드의 두 가지 차이
차이 1: 개행 문자 변환 (Universal Newlines)
운영체제마다 파일에 저장하는 개행 문자가 다르다.
- Unix/Linux/macOS:
\n(LF, 1바이트) - Windows:
\r\n(CRLF, 2바이트) - 구버전 Mac:
\r(CR, 1바이트, 거의 사라짐)
텍스트 모드('r', 'w')에서 Python은 Universal Newlines를 적용한다.
- 읽기: 파일의
\r\n,\r을 모두\n으로 변환한 뒤 반환 - 쓰기:
\n을 현재 OS의 기본 개행 문자로 변환
# Windows 파일 (내부 \r\n) — 텍스트 모드
with open("windows_file.txt", "r", encoding="utf-8") as f:
content = f.read()
# content의 모든 \r\n → \n 으로 변환됨
# 이진 모드 — 변환 없음
with open("windows_file.txt", "rb") as f:
raw = f.read()
# raw에는 \r\n 그대로 포함
이 변환은 이진 파일에서는 재앙이다. 이미지 파일에 우연히 0x0D 0x0A (CRLF와 동일한 바이트 쌍)가 있으면, 텍스트 모드로 읽을 때 0x0A (LF)로 바뀌어버린다. 파일이 손상된다.
# ❌ 이미지를 텍스트 모드로 열면 손상
with open("photo.jpg", "r") as f: # 위험!
corrupted = f.read()
# ✅ 이진 파일은 반드시 'rb'
with open("photo.jpg", "rb") as f:
original = f.read() # 원본 그대로
차이 2: 인코딩/디코딩 레이어
텍스트 모드에서 read()는 디스크의 바이트를 encoding 매개변수에 따라 디코딩해 str을 반환한다. write()는 반대로 str을 인코딩해 바이트를 저장한다.
이진 모드에서는 이 레이어가 없다. read()는 bytes를 반환하고, write()는 bytes만 받는다.
# 텍스트 모드: str 자동 변환
with open("text.txt", "w", encoding="utf-8") as f:
f.write("안녕하세요") # str → bytes 자동
with open("text.txt", "r", encoding="utf-8") as f:
s = f.read() # bytes → str 자동
print(type(s)) # <class 'str'>
# 이진 모드: bytes 직접 처리
with open("text.bin", "wb") as f:
f.write("안녕하세요".encode("utf-8")) # 직접 인코딩 필요
# f.write("안녕하세요") # → TypeError: str not bytes
with open("text.bin", "rb") as f:
b = f.read()
print(type(b)) # <class 'bytes'>
s = b.decode("utf-8") # 직접 디코딩 필요
실전 코드
CSV 파일과 newline=''
csv 모듈 공식 문서는 CSV 파일을 열 때 newline=''를 사용하도록 권장한다.
import csv
# ✅ csv.reader/writer 사용 시 newline=''
with open("data.csv", "r", encoding="utf-8", newline="") as f:
reader = csv.reader(f)
for row in reader:
print(row)
with open("out.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["이름", "나이"])
writer.writerow(["Alice", 30])
newline=""을 지정하면 Python의 Universal Newlines 변환이 비활성화되고 csv 모듈이 직접 개행 처리를 담당한다. 이 옵션 없이 열면 Windows에서 각 행 뒤에 빈 줄이 추가되는 문제가 생긴다.
newline 매개변수 완전 정리
# newline=None (기본): Universal Newlines 활성화
# newline='' : 변환 없이 \r\n, \r을 그대로 반환 (csv용)
# newline='\n': \r\n → \n 변환, \r은 그대로
# newline='\r\n': 쓸 때 \n → \r\n 변환
# 각 OS의 줄 끝을 명시적으로 제어하고 싶을 때
with open("crossplatform.txt", "w", newline="\r\n") as f:
f.write("Windows 형식\n") # → 파일에 \r\n 저장
언제 어떤 모드를 써야 하나
| 파일 종류 | 올바른 모드 | 이유 |
|---|---|---|
.txt, .md, .py, .html |
'r' / 'w' |
텍스트, 인코딩 처리 필요 |
.csv, .tsv |
'r', newline='' |
csv 모듈과 함께 사용 |
.jpg, .png, .gif |
'rb' / 'wb' |
이진 데이터, 변환 금지 |
.pdf, .docx, .zip |
'rb' / 'wb' |
이진 형식 |
.mp3, .mp4, .avi |
'rb' / 'wb' |
이진 데이터 |
| 소켓에서 받은 데이터 | 'wb' 또는 직접 처리 |
이진 스트림 |
이진 파일과 텍스트 파일을 구분하는 간단한 방법은 "파일을 메모장으로 열었을 때 사람이 읽을 수 있는가"다. 읽을 수 있으면 텍스트 모드, 깨진 글자나 이상한 기호가 가득하면 이진 모드가 맞다.
io 모듈: 스트림 클래스 계층
텍스트 모드와 이진 모드의 차이는 io 모듈의 스트림 클래스에서 나온다.
import io
# 텍스트 모드 open() → io.TextIOWrapper 반환
with open("a.txt", "r") as f:
print(type(f)) # <class '_io.TextIOWrapper'>
# 이진 모드 open() → io.BufferedReader 반환
with open("a.jpg", "rb") as f:
print(type(f)) # <class '_io.BufferedReader'>
# 메모리 내 텍스트 스트림 (테스트용)
buf = io.StringIO("line1\nline2\n")
for line in buf:
print(line.strip())
# 메모리 내 이진 스트림
binbuf = io.BytesIO(b"\x89PNG\r\n")
header = binbuf.read(4)
io.StringIO와 io.BytesIO는 파일 없이 메모리에서 파일처럼 데이터를 처리할 때 유용하다. 단위 테스트에서 실제 파일 없이 파일 객체를 흉내 낼 때 자주 쓰인다.
지난 글: with 문과 컨텍스트 매니저: 자원을 안전하게 관리하는 법
다음 글: 파일 인코딩: UTF-8부터 EUC-KR까지
읽어주셔서 감사합니다. 😊

