Daylogs/Python
Python: 임시파일(TemporaryFile)에 유니코드 캐릭터 쓰기
ohgyun
2017. 2. 27. 00:07
발생일: 2016.04.14
키워드: python Temporary file, 파이썬 임시파일, tempfile.TemporaryFile, unicode, utf8, 유니코드, Create temporary file with unicode encoding, unicode object, 유니코드 객체
문제:
파일 내의 특정 패턴의 단어를 다른 단어로 치환하려고 한다.
다음 순서대로 실행하려고 했다.
1. 원본 파일을 라인 단위로 읽어 특정 단어를 치환한 후에,
2. 치환한 라인을 순서대로 임시 파일에 쓴다.
3. 전체 파일을 다 읽었다면, 임시 파일의 내용을 원본 파일에 복사한다.
임시 파일은 tempfile 모듈을 이용해 생성했는데, 임시 파일을 쓰려고 하니 아래와 같은 에러가 발생했다.
UnicodeEncodeError: 'ascii' codec can't encode character ...
왜 그런걸까?
해결책:
유니코드 객체를 인코딩 없이 임시 파일에 쓰려고 했던 게 문제였다.
TemporaryFile에서는 별도로 인코딩을 설정하는 옵션이 없기 때문에,
읽어온 라인을 utf-8로 인코딩해서 쓰도록 수정했다.
아래 강조한 부분이 수정한 부분이다.
def replace_characters(filepath):
in_file = codecs.open(filepath, 'r', 'utf-8')
out_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
for line in in_file:
new_line = replace_specific_pattern(line)
out_file.write(new_line.encode('utf-8'))
in_file.close()
out_file.close()
shutil.copy(out_file.name, filepath)
논의:
파이썬에서는 유니코드 데이터를 다루는 별도의 unicode 타입이 있고,
u'가'와 같이 유니코드 타입을 생성할 수 있다.
>>> type('가')
<type 'str'>
>>> type(u'가')
<type 'unicode'>
unicode와 str을 서로 변환할 때엔, unicode.encode()와 str.decode()를 사용하면 된다.
- unicode.encode(character_set):
유니코드 객체를 특정 캐릭터셋을 이용해 스트링 바이트로 인코딩한다.
- str.decode(character_set):
스트링 바이트를 특정 캐릭터셋을 이용해 유니코드 객체로 디코딩한다.
(encode()는 unicode에서 호출하고, decode()는 str에서 호출하는 것에 주의한다)
아래처럼 유니코드 객체를 스트링 바이트로 인코딩할 수 있다.
>>> a = u'가'.encode('utf8')
>>> type(a)
<type 'str'>
>>> a
'\xea\xb0\x80'
utf8 대신 다른 캐릭터셋을 사용할 수도 있다.
>>> b = u'가'.encode('euckr')
>>> type(b)
<type 'str'>
>>> b
'\xb0\xa1'
유니코드 객체로부터 인코딩해 만든 스트링 객체를 디코딩하면, 다시 유니코드 객체를 얻을 수 있다.
>>> a2 = a.decode('utf8')
>>> type(a2)
<type 'unicode'>
>>> a2
u'\uac00'
>>> a2 == u'가'
True
문제의 상황은,
파일을 특정 캐릭터셋으로 읽어온 값이 유니코드 객체였는데,
별도의 인코딩을 지원하지 않는 임시 파일에 유니코드 객체를 쓰려고 했기 때문이었다.
아래 코드와 주석을 보면 원인과 해결 방법을 이해할 수 있다.
def replace_characters(filepath):
# 이해를 쉽게 하기 위해, codecs 대신 open()으로 읽게 변경했다
with open(filepath, 'r') as input:
in_file = input.read()
type(in_file) #-> <type 'str'>
in_file = in_file.decode('utf-8')
type(in_file) #-> <type 'unicode'>
out_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
for line in in_file:
new_line = replace_specific_pattern(line)
type(new_line) #-> <type 'unicode'>
encoded = new_line.encode('utf-8')
type(encoded) #-> <type 'str'>
out_file.write(encoded)
in_file.close()
out_file.close()
shutil.copy(out_file.name, filepath)
참고:
반응형