CSV나 텍스트 파일을 읽다가 UnicodeDecodeError가 나면, 먼저 파일을 저장한 인코딩과 읽을 때 지정한 인코딩이 같은지 확인해야 합니다. encoding="utf-8"을 붙이는 것만으로 모든 한글 파일이 읽히지는 않습니다. CP949로 저장된 파일은 encoding="cp949"로 읽어야 합니다.
특히 Python 3.15에서는 인코딩을 생략했을 때의 기본값이 UTF-8로 바뀝니다. 예전 Windows 환경에서 잘 읽히던 파일이 업그레이드 뒤에 실패하는 경우도 이 차이로 설명할 수 있습니다. 이미 encoding을 명시한 코드는 그 설정을 따릅니다. Python 3.15 공식 변경점
확인 기준은 2026년 9월 30일, Windows에서 실행한 Python 3.14.7과 3.15.0rc2입니다. 아래 3.15 실행 결과는 정식판 전 릴리스 후보인 3.15.0rc2의 결과입니다. 2026년 10월 5일 추가 확인 : Python 3.15.0rc3는 10월 2일 공개됐으며, 정식판 예정일은 기존 10월 1일에서 10월 9일로 변경됐습니다. 공식 출시 일정

오류에 나온 utf-8은 파일의 정답 인코딩이 아닙니다
다음은 CP949 예제 파일을 UTF-8로 읽었을 때 나온 오류의 마지막 줄입니다.
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbb in position 0: invalid start byte
인코딩은 글자를 파일의 숫자 데이터로 저장하는 규칙입니다. 파일에는 바이트라는 숫자 단위의 데이터가 들어 있고, 파이썬은 이를 문자열로 바꿔 읽습니다. 이 반대 방향의 변환을 디코딩이라고 합니다. UTF-8과 CP949는 서로 다른 인코딩 이름입니다.
이 오류는 “UTF-8 규칙으로 읽으려 했는데 해당 바이트를 해석할 수 없다”는 뜻입니다. 파일이 UTF-8이라는 뜻도, 반드시 CP949라는 뜻도 아닙니다. 오류에 표시된 인코딩은 실패한 읽기 규칙입니다. 파일에 따라 바이트 값과 위치는 달라집니다.
흐름은 파일의 바이트 → 지정한 인코딩으로 해석 → 파이썬 문자열입니다. 읽기 규칙을 바꿔도 원본 파일의 바이트는 바뀌지 않습니다. 파일 자체를 바꾸려면 올바르게 읽은 문자열을 다른 인코딩으로 새로 저장해야 합니다.
파일 출처를 확인하고 encoding을 고릅니다
파일을 만든 프로그램의 내보내기 옵션이나 제공처의 설명에서 인코딩을 확인합니다. 아래 표는 그 인코딩으로 저장됐음을 확인한 경우의 선택 기준입니다.
| 파일의 저장 형식 | 읽을 때 지정할 값 | 확인할 점 |
|---|---|---|
| UTF-8 텍스트 | encoding="utf-8" |
일반 UTF-8 파일 |
| BOM이 붙은 UTF-8 텍스트 | encoding="utf-8-sig" |
맨 앞의 BOM 표시를 제외하고 읽음 |
| CP949 텍스트 | encoding="cp949" |
한글 Windows에서 생성한 일부 기존 자료 |
| 형식을 모르는 파일 | 먼저 제공처와 저장 옵션 확인 | 확장자나 오류 이름만으로 확정하지 않음 |
CSV는 표의 행과 열을 구분하는 형식이지 인코딩 이름이 아닙니다. .csv라고 해서 모두 UTF-8이거나 모두 CP949인 것은 아닙니다. .xlsx 역시 CSV가 아니므로, 이름만 .csv로 바꾼 뒤 텍스트처럼 읽으면 안 됩니다.
여러 인코딩을 시도해서 오류가 사라졌더라도 실제 상품명이나 한글 문장이 맞는지 확인합니다. 잘못된 인코딩으로 읽었는데도 오류 없이 다른 글자가 나오는 경우가 있기 때문입니다.
CP949로 저장된 파일이라면 이렇게 읽습니다
open()은 파이썬이 기본 제공하는 파일 열기 함수입니다. 괄호 안에는 파일 이름, 읽기 모드 "r", 문자 해석 규칙을 넣고, 결과로 열린 파일을 돌려받습니다. read()는 그 파일에 붙여 호출하는 함수인 메서드이며, 읽은 내용을 문자열로 돌려줍니다.
with ... as file은 열린 파일을 file이라는 이름으로 사용한 뒤 닫는 구문입니다. print()는 화면에 값을 출력하고, end=""는 출력 끝에 줄바꿈을 추가하지 않도록 합니다. 다음 코드는 원본 파일을 수정하지 않습니다. 직접 따라 할 경우 다음 절에서 예제 파일을 먼저 만든 뒤 실행하세요.
with open("sample_cp949.csv", "r", encoding="cp949") as file:
text = file.read()
print(text, end="")
with ... as file은 열린 파일을file이라는 이름으로 쓰고, 들여쓰기한 부분이 끝나면 닫는 구문입니다.encoding="cp949"는 파일의 바이트를 CP949 규칙으로 해석하라는 뜻입니다.text = file.read()는 오른쪽에서 읽은 문자열을 왼쪽의text에 저장합니다.print(text, end="")는 문자열을 출력하되,print()가 추가하는 마지막 줄바꿈은 생략합니다.
아래 실습에서 만든 파일이라면 결과는 다음과 같습니다.
상품,수량
사과,2
실제 파일이 UTF-8이라면 위 코드의 encoding을 "utf-8"로 맞춥니다. CP949를 모든 오류의 해결값으로 고정하는 것도 올바르지 않습니다.
같은 파일로 오류와 해결을 직접 비교해 봅니다
실습용 빈 폴더를 하나 만들고, 그 폴더를 터미널의 현재 위치로 열어 진행합니다. 다음 코드를 make_sample.py로 저장하세요. 파이썬 코드는 UTF-8로 저장합니다.
"x"는 같은 이름의 파일이 없을 때만 새 파일을 만드는 모드입니다. write()는 문자열을 파일에 쓰는 기능이고, \n은 줄바꿈입니다. newline=""은 코드의 \n을 운영체제 줄바꿈으로 바꾸지 않고 그대로 저장하는 설정입니다. 여기서는 CP949 파일을 일부러 만듭니다.
with open("sample_cp949.csv", "x", encoding="cp949", newline="") as file:
file.write("상품,수량\n사과,2\n")
터미널에서 실행합니다. 이미 같은 파일이 있으면 FileExistsError가 나므로, 새 폴더를 사용하거나 예제의 파일 이름을 바꾸세요.
python make_sample.py
이제 read_wrong.py를 만들고, 같은 파일을 UTF-8로 읽게 합니다.
with open("sample_cp949.csv", "r", encoding="utf-8") as file:
print(file.read())
python read_wrong.py
두 버전에서 모두 앞서 본 'utf-8' codec can't decode byte 0xbb 오류를 확인했습니다. 이 코드의 "utf-8"을 "cp949"로 바꾸어 다시 실행하면 상품,수량과 사과,2가 출력됩니다. 파일을 다시 만들거나 파이썬을 재설치할 필요는 없습니다.
FileNotFoundError가 나온다면 인코딩 비교 단계까지 가지 못한 것입니다. 먼저 파일이 있는데도 못 찾는 상대경로 문제에서 현재 작업 폴더를 확인하세요.
Python 3.15에서는 생략한 encoding을 점검합니다
다음 코드는 같은 CP949 파일을 열되, encoding을 생략합니다. import sys는 실행 중인 파이썬의 정보를 제공하는 기본 모듈을 불러옵니다. sys.flags.utf8_mode의 1은 UTF-8 모드가 켜져 있다는 뜻이고, file.encoding은 열린 파일에 적용된 인코딩 이름입니다.
check_encoding.py로 저장합니다.
import sys
print("UTF-8 mode:", sys.flags.utf8_mode)
with open("sample_cp949.csv", "r") as file:
print("File encoding:", file.encoding)
print(file.read(), end="")
CP949 로캘인 테스트 Windows에서 얻은 결과입니다. 로캘은 운영체제의 언어와 지역 관련 설정이며, 모든 Windows PC의 기본 인코딩이 같지는 않습니다.
| 실행 조건 | UTF-8 mode | File encoding | CP949 예제 파일 결과 |
|---|---|---|---|
| Python 3.14.7 기본 설정 | 0 |
cp949 |
정상 출력 |
Python 3.14.7에 -X utf8 적용 |
1 |
utf-8 |
UnicodeDecodeError |
| Python 3.15.0rc2 기본 설정 | 1 |
utf-8 |
UnicodeDecodeError |
Python 3.15.0rc2에 -X utf8=0 적용 |
0 |
cp949 |
정상 출력 |
Python 3.14에서도 UTF-8 모드를 켜면 같은 문제를 미리 확인할 수 있습니다. -X utf8은 이 명령으로 시작한 파이썬에 UTF-8 모드를 적용하는 옵션입니다.
python --version
python -X utf8 check_encoding.py
첫 명령에서 실제 실행 버전을 확인합니다. 여러 버전이 설치됐다면 python이 가리키는 버전부터 확인해야 비교가 맞습니다. 3.14에서 UTF-8 모드를 켠 테스트는 인코딩 변경의 영향을 보는 것이며, 3.15의 모든 변경 사항을 검증하는 것은 아닙니다.
인코딩을 빠뜨린 위치를 찾을 때는 Python 3.10 이상에서 다음 경고 옵션을 쓸 수 있습니다.
python -X utf8=0 -X warn_default_encoding check_encoding.py
이 예제에서는 open()이 있는 줄에 다음 경고가 표시됩니다.
EncodingWarning: 'encoding' argument not specified
원인을 비교할 때 -X utf8=0으로 이전 동작을 확인할 수는 있습니다. 다만 CP949 파일을 계속 읽어야 한다면 그 파일을 여는 코드에 encoding="cp949"를 명시하는 편이 운영체제와 실행 옵션에 덜 의존합니다. encoding="locale"은 현재 운영체제 로캘을 따르는 별도 선택지이며, 고정된 CP949 파일을 다른 PC에서도 읽어야 하는 경우와는 목적이 다릅니다. PEP 686의 호환성 안내
utf-8-sig가 필요한 경우는 따로 있습니다
BOM은 파일 맨 앞에 붙는 표시입니다. UTF-8 BOM이 있는 파일을 "utf-8"로 읽으면 맨 앞에 보이지 않는 \ufeff 문자가 남을 수 있습니다. CSV에서는 첫 번째 열 이름이 예상과 달라지는 원인이 됩니다.
"utf-8-sig"로 읽으면 맨 앞의 UTF-8 BOM을 건너뜁니다. BOM이 없는 일반 UTF-8 파일도 읽을 수 있지만, CP949를 UTF-8로 바꾸어 주는 옵션은 아닙니다. Python utf_8_sig 문서
BOM이 있는 UTF-8 CSV를 읽는 예입니다. csv는 파이썬에 포함된 CSV 처리 모듈이고, csv.reader(file)은 파일에서 한 행씩 읽어 각 행의 열 값을 리스트로 돌려줍니다. newline=""은 줄바꿈 처리를 CSV 모듈에 맡기는 설정입니다.
import csv
with open("sample_utf-8-sig.csv", "r", encoding="utf-8-sig", newline="") as file:
for row in csv.reader(file):
print(row)
위 파일은 BOM이 있는 UTF-8 파일을 준비했을 때의 예입니다. 직접 만들려면 앞의 make_sample.py에서 파일 이름을 sample_utf-8-sig.csv, 저장 인코딩을 "utf-8-sig"로 바꿔 실행합니다. 출력은 다음과 같습니다.
['상품', '수량']
['사과', '2']
행과 열을 반복해서 처리하는 과정은 파이썬 CSV 파일 읽기 쓰기에서 이어서 볼 수 있습니다.
UTF-8로 통일하려면 원본을 남기고 새 파일에 저장합니다
CP949 파일을 올바르게 읽은 뒤 UTF-8로 저장하면 이후에는 UTF-8로 읽을 수 있습니다. 아래 코드는 작은 텍스트 파일용으로, 내용 전체를 메모리에 읽습니다. 큰 파일은 줄 단위 처리 등 별도 설계가 필요합니다.
with open("sample_cp949.csv", "r", encoding="cp949", newline="") as source:
text = source.read()
with open("sample_converted_utf8.csv", "x", encoding="utf-8", newline="") as target:
target.write(text)
print("sample_converted_utf8.csv 저장 완료")
첫 번째 with는 CP949 바이트를 문자열로 읽습니다. 두 번째는 같은 문자열을 UTF-8 바이트로 저장합니다. 원본 파일은 그대로 남고, 새 파일 이름이 이미 존재하면 "x" 모드가 덮어쓰기를 막습니다.
실행 후 새 파일을 encoding="utf-8"로 다시 읽어 상품,수량과 사과,2가 그대로 나오는지 확인했습니다. 원본 바이트도 변경되지 않았습니다. 실제 자료를 변환할 때도 주요 한글 값과 행 수를 비교한 뒤 변환본을 사용하세요.
오류만 사라지고 글자가 없어지는 해결법은 피합니다
errors="ignore"는 해석하지 못한 데이터를 버리는 옵션입니다. errors="replace"는 해석하지 못한 부분을 대체 문자로 바꿉니다. 둘 다 원래 인코딩을 찾아 한글을 복원하는 기능은 아닙니다.
CP949 예제 파일을 UTF-8과 errors="ignore"로 읽어 보니 상품과 사과가 보존되지 않았습니다. 주문명이나 이름 같은 데이터라면 오류 없이 실행되는 것만으로 성공을 판단할 수 없습니다. 읽어 온 글자가 원본 내용과 같은지 확인해야 합니다.
또한 .py 파일 첫 줄의 # -*- coding: utf-8 -*-은 파이썬 소스 코드 자체의 문자 해석에 관한 표시입니다. open()으로 읽는 CSV의 인코딩을 지정하지는 않습니다. 터미널 출력 인코딩을 바꾸는 PYTHONIOENCODING도 일반 파일의 open() 인코딩을 대신 정하지 않습니다. Python UTF-8 모드와 출력 인코딩
UnicodeDecodeError가 계속되면 파일 제공처의 인코딩, 파일이 실제 텍스트 형식인지, 다운로드가 정상적으로 끝났는지부터 다시 확인합니다. 파일 읽기 구문이 아직 낯설다면 with open으로 파일 읽고 쓰기를 먼저 연습하세요. 새로 저장하는 파일은 읽는 쪽과 인코딩을 맞추고, 기존 파일은 실제 저장 규칙을 확인한 뒤 읽는 것이 기준입니다.
참고 문서
'IT프로그래밍 > 파이썬' 카테고리의 다른 글
| pip No matching distribution found 해결 : 패키지 이름과 Python 버전 확인 (0) | 2026.10.05 |
|---|---|
| 파이썬 CSV 파일 읽기 쓰기 : reader와 writer로 주문 목록 저장하기 (0) | 2026.09.23 |
| 파이썬 JSON 파일 저장과 읽기 : dump와 load로 딕셔너리 보관하기 (0) | 2026.09.10 |
| 파이썬 FileNotFoundError 해결 : 파일이 있는데도 못 찾는 상대경로 문제 (0) | 2026.09.09 |
| 파이썬 import 사용법 : 직접 만든 모듈에서 함수 불러오기 (0) | 2026.09.07 |
| 파이썬 ModuleNotFoundError 해결 : pip와 실행 Python 경로 맞추기 (0) | 2026.09.04 |
| 파이썬 t-string 사용법 : f-string과 다른 점과 Python 3.14 처리 원리 (0) | 2026.09.02 |
| 파이썬 sorted와 sort 차이 : key와 lambda로 원하는 기준 정렬하기 (0) | 2026.09.01 |
댓글