Python 2의 str·unicode와 한글 인코딩 오류
Python 2 코드를 유지보수할 때 한글이 들어간 문자열에서 UnicodeDecodeError가 발생할 수 있습니다. 소스 파일의 인코딩 선언과 실행 중 문자열의 변환 문제를 나눠 봐야 합니다.
아래는 Python 2 환경을 설명하는 예시입니다. Python 3의 str·bytes와 같은 이름으로 해석하면 혼동하기 쉽습니다.
Str과 Unicode의 차이
Python 2의 str은 바이트 문자열입니다. 그 값이 언제나 ASCII라는 뜻은 아닙니다. UTF-8이나 다른 인코딩의 바이트도 들어갈 수 있습니다. unicode는 문자를 표현하며 바이트로 바꾸려면 인코딩이 필요합니다.
UTF-8 소스에서 다음처럼 두 타입을 섞으면 암묵적인 변환 중 오류가 날 수 있습니다.
# -*- coding: utf-8 -*-
raw = "안녕" # UTF-8 바이트인 str
text = u"하세요" # unicode
result = raw + text이때 Python 2가 바이트를 기본 ASCII로 해석하려 하면 한글 바이트를 읽지 못합니다. Python 2 Unicode HOWTO 에 타입과 변환 동작이 정리돼 있습니다.
소스 파일의 인코딩 선언
# -*- coding: utf-8 -*-
name = u"홍길동"파일의 인코딩 선언은 소스에 적힌 문자를 어떻게 읽을지 알려 줍니다. 파일이나 네트워크에서 들어온 모든 데이터의 인코딩까지 바꾸는 설정은 아닙니다.
입력에서 Decode, 출력에서 Encode
바이트의 실제 인코딩을 알고 있다면 읽을 때 해당 인코딩으로 변환합니다.
# -*- coding: utf-8 -*-
raw = "안녕하세요"
text = raw.decode("utf-8")
output = text.encode("utf-8")처리 중에는 unicode를 사용하고, 파일에 쓰거나 통신으로 보낼 때 바이트가 필요하면 encode하는 방식으로 구분할 수 있습니다. 입력이 다른 인코딩이라면 그에 맞는 codec을 사용해야 합니다.
파일도 인코딩을 지정해 읽고 쓸 수 있습니다.
# -*- coding: utf-8 -*-
import codecs
with codecs.open("input.txt", "r", encoding="utf-8") as source:
text = source.read()
with codecs.open("output.txt", "w", encoding="utf-8") as target:
target.write(text)기본 인코딩 변경보다 변환 지점 확인
reload(sys)와 sys.setdefaultencoding으로 전역 기본값을 바꾸기보다, 어느 데이터가 어떤 인코딩의 바이트인지 확인하는 편이 원인을 찾기 쉽습니다. 이미 디코딩된 값을 다시 변환하는지와 서로 다른 인코딩이 섞였는지도 봐야 합니다.
Python 3으로 옮길 때도 입출력 데이터의 인코딩을 알아야 한다는 점은 같습니다. 타입이 str과 bytes로 나뉘어 있어도 잘못된 codec으로 읽는 문제까지 자동으로 해결되지는 않습니다.