파이썬 2.7에서 한글 들어간 텍스트 파일 읽다가 또 UnicodeDecodeError. 매번 까먹어서 정리.
기본부터 다시 짚고 가자. 2.7은 str이 bytes고 unicode가 유니코드 코드포인트 시퀀스다. 파일 읽기는 기본적으로 bytes를 돌려주고, 그 바이트열을 어떤 인코딩으로 해석할지는 개발자가 결정해야 함. open()은 encoding 인자가 없으니 codecs 써야 한다.
# -*- coding: utf-8 -*-
import codecs
# 읽기 — euc-kr 로 저장된 파일
with codecs.open("input.txt", "r", encoding="euc-kr") as f:
text = f.read() # 여기서 text 타입은 unicode
# 쓰기 — utf-8 로 내보내기
with codecs.open("output.txt", "w", encoding="utf-8") as f:
f.write(text)
주의사항 몇 가지.
1. codecs.open 으로 읽은 결과는 이미 unicode. 여기서 .decode("utf-8") 또 호출하면 에러. 파이썬 2의 유니코드 객체에 .decode를 부르면 내부적으로 먼저 ascii로 encode → 다시 decode 하는 괴상한 경로를 타기 때문에 한글 들어있으면 바로 터진다. 내가 매번 하는 실수.
2. stdout 인코딩 문제. 터미널에 print u"안녕" 하면 되다가, 이걸 파일로 리다이렉트하면 sys.stdout.encoding이 ascii가 되면서 UnicodeEncodeError: 'ascii' codec can't encode .... 해결책:
import sys, codecs
sys.stdout = codecs.getwriter("utf-8")(sys.stdout)
3. cp949 vs euc-kr. 윈도우에서 온 파일은 euc-kr이 아니라 cp949로 읽어야 한다. cp949는 euc-kr 상위집합이라 "똠방각하" 같은 확장문자를 euc-kr로 디코드하면 예외가 난다. 반대로 cp949로 열면 둘 다 읽힘. 맥/리눅스에서 만든 euc-kr 파일도 cp949로 읽어 실용상 문제 없다.
4. BOM. 윈도우 메모장이 UTF-8로 저장하면 앞에 \xef\xbb\xbf(BOM) 붙인다. utf-8로 열면 맨 앞 글자가 \ufeff로 들어와서 "왜 키가 안 맞지"로 하루 날림. utf-8-sig 쓰면 BOM 자동 제거.
5. 모듈 기본 인코딩. 옛날 코드에서 sys.setdefaultencoding("utf-8") 하는 경우 있는데 이건 지양. sitecustomize나 reload(sys) 꼼수 말고, 필요한 시점에 명시적으로 encode/decode 하는 게 맞다. 암묵 변환에 의존하면 다른 사람 코드랑 섞일 때 재앙.
6. CSV 읽기. csv 모듈은 2.7에서 유니코드를 직접 못 다룬다. bytes 레벨로 읽은 뒤 각 필드를 decode 해줘야 함. 정석은 unicodecsv 서드파티 패키지. 회사 내부 배치는 일단 list(csv.reader(open(f))) → 각 row comprehension으로 decode.
import csv
rows = [[c.decode("cp949") for c in row]
for row in csv.reader(open("a.csv", "rb"))]
7. JSON. json.dumps는 default로 ensure_ascii=True라 한글이 \u314f 식으로 나간다. API 응답 찍어보다가 당황하는 포인트. ensure_ascii=False 주고 대신 encode("utf-8")로 명시적으로 bytes로 변환.
파이썬 3에서는 이 모든 게 많이 정리됐다고 한다(str == unicode, bytes 분리). 레거시가 2.6/2.7이라 당분간 이렇게 살아야 함. 언젠가는 3으로 옮길 텐데 유니코드 → 문자열 리터럴, print() 함수화, urllib 재편 같은 걸 어떻게 처리할지 생각해두는 중.