파이썬에서 유니코드 스트림 다루기
원문 - Working with unicode streams in Python
번역을 허락해 준 Dave Hall 님께 고마움을 전합니다.
파이썬에서 유니코드를 다룰 때는 일반적으로 str.decode()
와 unicode.encode()
메서드를 사용하여 unicode
타입과 str
타입을 상호 변환한다.
아래 예시에서는 'utf-16'으로 작성된 파일을 열어, 수직 탭(vertical tab) 코드포인트를 지운 다음, 'utf-8'로 저장한다. (깨진 XML을 다룰 때 이 방식이 매우 중요하다.)
# 파일 내용을 읽는다
with open("input.txt", "rb") as input:
data = input.read()
# 바이너리 데이터를 utf-16으로 디코딩한다
data = data.decode("utf-16")
# 수직 탭을 삭제한다
data = data.replace(u"\u000B", u"")
# 유니코드 데이터를 utf-8로 인코딩한다
data = data.encode("utf-8")
# 데이터를 utf-8로 저장한다
with open("output.txt", "wb") as output:
output.write(data)
엄청나게 큰 파일을 다룰 때가 아니라면 이 정도로도 충분하다. 하지만 큰 파일을 다룰 땐 모든 데이터가 메모리에 올라간다는 사실이 문제가 된다.
스트리밍 인코더/디코더 사용하기
파이썬 기본 라이브러리에는 codecs
모듈이 포함되어 있다. 이 모듈을 사용하면 파일을 조금씩 읽을 수 있고, 메모리에도 약간의 유니코드 데이터만 올라가게 된다.
codecs.open()
헬퍼 메서드를 사용하여 위의 예시를 최소한만 고쳐보자.
import codecs
# 입력 스트림과 출력 스트림을 연다
input = codecs.open("input.txt", "rb", encoding="utf-16")
output = codecs.open("output.txt", "wb", encoding="utf-8")
# 유니코드 데이터 조각들을 스트리밍한다
with input, output:
while True:
# 데이터 조각을 읽고
chunk = input.read(4096)
if not chunk:
break
# 수직 탭을 삭제한다
chunk = chunk.replace(u"\u000B", u"")
# 데이터 조각을 쓴다
output.write(chunk)
파일은 끔찍해! 이터레이터 사용하기
파일은 다루기가 좀 지루하다. 복잡한 처리 과정에는 유니코드 데이터의 이터레이터를 다루는 편이 깔끔할 것이다.
아래는 iterdecode()
를 사용하여, 파일을 유니코드 데이터 조각의 이터레이터로 읽는 효과적인 방법이다.
from functools import partial
from codecs import iterdecode
# 특정 path의 파일을 유니코드 조각의 이터레이터로 리턴한다
def iter_unicode_chunks(path, encoding):
# 읽을 파일을 연다
with open(path, "rb") as input:
# 바이너리 파일을 바이너리 조각으로 변환한다
binary_chunks = iter(partial(input.read, 1), "")
# 바이너리 조각을 유니코드 조각으로 변환한다
for unicode_chunk in iterdecode(binary_chunks, encoding):
yield unicode_chunk
이제 iterencode()
메서드를 사용하여, 유니코드 조각의 이터레이터를 파일에 써보자.
from codecs import iterencode
# 유니코드 조각의 이터레이터를 특정 path의 파일에 쓴다
def write_unicode_chunks(path, unicode_chunks, encoding):
# 쓸 파일을 연다
with open(path, "wb") as output:
# 유니코드 조각을 바이너리로 변환한다
for binary_chunk in iterencode(unicode_chunks, encoding):
output.write(binary_chunk)
이 두 함수와 함께 유니코드 데이터의 스트림에서 수직 탭을 없애는 일이 마법 같이 끝난다(just becomes a case of plumbing everything together).
# 파일을 유니코드 조각 형태로 읽는다
unicode_chunks = iter_unicode_chunks("input.txt", encoding="utf-16")
# 유니코드 조각을 수정한다
unicode_chunks = (
chunk.replace(u"\u000B", u"")
for chunk
in unicode_chunks
)
# 유니코드 조각을 파일에 저장한다
write_unicode_chunks("output.txt", unicode_chunks, encoding="utf-8")
거창하게 codecs
모듈을 사용해야 할까?
얼핏 그냥, str.decode()
와 unicode.encode()
메서드를 사용하여 큰 file
객체를 바이너리 조각으로 읽고, 인코딩하고 디코딩하는 편이 간단하다고 생각할 수도 있겠다.
# 나쁜 예시. 이렇게 하지 마시오!
# 입력 스트림과 출력 스트림을 연다
with open("input.txt", "rb") as input, open("output.txt", "wb") as output:
# 바이너리 데이터 조각들을 순회한다
while True:
# 데이터 조각을 읽는다
chunk = input.read(4096)
if not chunk:
break
# 위험: 바이너리 데이터를 utf-16으로 디코딩한다
chunk = chunk.decode("utf-16")
# 수직 탭을 삭제한다
chunk = chunk.replace(u"\u000B", u"")
# 유니코드 데이터를 utf-8로 인코딩한다
chunk = chunk.encode("utf-8")
# 데이터 조각을 쓴다
output.write(chunk)
불행히도 몇몇 유니코드 코드포인트는 바이너리 데이터의 한 바이트 이상으로 인코딩된다. 따라서 단순히 파일에서 바이트 조각들을 읽어서 decode()
메서드를 적용하면 예기치 않게 UnicodeDecodeError
가 발생할 수도 있다. 이는 바이트 한 조각이 여러 바이트의 코드포인트로 분리되었기 때문이다.
codecs
모듈의 도구들을 사용하면 이러한 예기치 않은 충돌을 예방할 수 있다.
파이썬 3에서는?
파이썬 3에서는 훨씬 단순하게 유니코드 파일을 다룰 수 있다. 빌트인 메서드인 open()
은 유니코드 데이터를 수정하거나 인코딩을 변경하는 데 필요한 기능을 포함하고 있다.
# 입력 스트림과 출력 스트림을 연다
input = open("input.txt", "rt", encoding="utf-16")
output = open("output.txt", "wt", encoding="utf-8")
# 유니코드 데이터 조각들을 스트리밍한다
with input, output:
while True:
# 데이터 조각을 읽고
chunk = input.read(4096)
if not chunk:
break
# 수직 탭을 삭제한다
chunk = chunk.replace("\u000B", "")
# 데이터 조각을 쓴다
output.write(chunk)
파이썬 3의 시대다! 즐겁게 코딩하길!
COMMENTS