유니코드(unicode)와 인코딩(encoding)
유니코드의 유래
컴퓨터는 여러분도 알다시피 결국 0과 1이라는 값만을 인식할 수 있는 기계장치이다. 컴퓨터가 문자를 인식할 수 있게 하려면 어떻게 해야 할까? 과거부터 지금까지 사용하는 유일한 방법은 다음과 비슷한 방법의 문자셋을 만드는 것이다.
예를 들어 숫자 0은 'a', 숫자 1은 'b', ... 이런식으로 숫자마다 문자를 매핑해 놓으면 컴퓨터는 해당 숫자를 문자열로 인식할 수 있게 될 것이다. 최초 컴퓨터가 발명되었을 때 이런 문자들을 처리하기 위해서 컴퓨터마다 각각의 문자셋을 정해 놓고 문자들을 처리하기 시작했다. 하지만 이렇게 컴퓨터들 마다 각각의 문자셋을 사용했더니 데이터 호환이 안되는 문제가 발생하게 되었다. A라는 컴퓨터에서 처리하는 문자셋 규칙이 B라는 컴퓨터에서 처리하는 문자셋 규칙과 같지 않기 때문에 서로 데이터를 주고 받는등의 일을 할 수가 없게 된것이다.
이런 문제를 해결하기 위해 미국에서 최초로 문자셋 표준인 아스키(ASCII)라는 것이 탄생하게 된다. 아스키라는 문자셋 규칙을 정하고 이 규칙대로만 문자를 만들면 이 기종 컴퓨터간에도 문제없이 데이터를 주고 받을 수 있게 되는 것이다. 아스키는 처리할 수 있는 문자의 갯수가 127개 였는데 이것은 영어권 국가들에서 사용하는 영문자, 숫자등을 처리하기 위해서는 부족함이 없었다. 하지만 곧 비 영어권 국가들에서도 자신들의 문자를 컴퓨터로 표현해야 하는 요구들이 발생하게 되었다. 아스키는 127개의 문자만을 다룰 수 있기 때문에 아스키를 사용할 수는 없는 노릇이었다. 그래서 곧 서유럽 문자셋인 ISO8859가 등장하게 되고 한국에서는 KSC5601과 같은 문자셋들이 등장하게 된다.
이렇게 나라별로 문자셋이 만들어지고 또 한 나라에서도 여러개의 문자셋이 표준이 되기 위한 치열한 싸움을 벌이기도 하며 문자를 처리하기 위한 방법은 점점 더 계속 복잡해져만 갔다.
가장 결정적인 문제는 하나의 문서에 여러나라의 언어를 동시에 표현할 수 있는 방법이 없었다는 점이다.
이런 문제를 해결하기 위해서 등장한 구세주가 바로 유니코드(Unicode)이다. 유니코드는 모든 나라의 문자를 다 포함하게끔 넉넉하게 설계되었고 곧 세계적인 표준으로 자리잡게 되었다. 이 유니코드라는 규칙을 사용하게 되면 서로 다른 문자셋으로 고생할 일이 이제 없어지게 된 것이다.
유니코드 vs utf-8
앞서 설명한 문자셋(character set)은 규칙이다. 이 규칙대로 바이트 데이터를 만들면 컴퓨터는 해당 문자셋을 해석할 수 있게 된다. 이렇게 문자셋의 규칙대로 바이트를 만드는 방법을 인코딩(encoding)이라고 부른다.
세계적인 표준 문자셋이 유니코드라고 했다. 이 유니코드 문자열을 바이트 문자열로 만드는 방법은 1가지였으면 좋았겠지만 여러가지 이유로 다양한 방법의 인코딩들이 등장하게 된다. 이 중에서 가장 많이 사용되는 것이 바로 utf-8이다. 가장 많이 사용되는 이유로는 다른 인코딩 방법에 비해 데이터의 사이즈가 작다는 점과 기존 ascii와의 하위 호환성을 갖는다는 점을 꼽을 수 있다. (utf-8을 이용하면 이미 아스키로 작성된 데이터라도 변환없이 utf-8 그대로 사용할 수 있게 된다.)
파이썬과 유니코드
이제 파이썬과 유니코드에 대해서 알아보자.
다음과 같은 문자열을 보자.
>>> a = "Life is too short"
>>> type(a)
<class 'str'>
type명령어를 호출해 보면 문자열은 str클래스의 객체임을 알 수 있다. 파이썬에서 사용되는 문자열은 모두 유니코드 문자열이다. (파이썬 3 버전부터는 모든 문자열을 유니코드로 처리하게 변경되었다.)
인코딩 (encoding)
유니코드 문자열을 인코딩없이 그대로 파일에 적거나 다른 시스템으로 네트워크 전송을 할 수는 없다. 왜냐하면 유니코드 문자열은 단순히 문자셋의 규칙이기 때문이다. 파일에 적거나 다른 시스템으로 유니코드 문자열을 전송하기 위해서는 바이트로 변환해야만 한다. 이렇게 유니코드 문자열을 바이트로 바꾸는 것을 인코딩이라고 한다. 따라서 파일을 읽거나 바이트 문자열을 수신받을 때에는 해당 바이트가 어떤 방식의 인코딩을 사용했는지는 필수적으로 미리 알고 있어야 디코딩이 가능하다.
유니코드 문자열을 바이트 문자열로 바꾸는 방법은 다음과 같다.
>>> b = a.encode('utf-8')
>>> b
b'Life is too short'
>>> type(b)
<class 'bytes'>
유니코드 문자열을 바이트 문자열로 만들 때에는 위 예처럼 인코딩 방식을 파라미터로 넘겨 주어야 한다. 파라미터를 생략하면 디폴트 값인 utf-8로 동작한다.
type 명령어를 호출해 보면 b객체는 bytes클래스의 객체임을 알 수 있다.
이번에는 다음 예제를 보자.
>>> a = "한글"
>>> a.encode("ascii")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
위 예는 "한글"이라는 유니코드 문자열을 ascii 방식으로 인코딩하려고 시도하는 예제이다. ascii 문자셋으로는 한글을 표현할 수 없기 때문에 위와 같은 오류가 발생한다.
"한글" 이라는 유니코드 문자열을 나타낼 수 있는 문자셋에는 여러개가 있다. 보통은 utf-8 로 충분하지만 이미 기존 시스템(legacy system)이 euc-kr과 같은 문자셋을 사용한다면 다음과 같이 euc-kr 로 인코딩할 수도 있다.
>>> a = '한글'
>>> a.encode('euc-kr')
b'\xc7\xd1\xb1\xdb'
>>> a.encode('utf-8')
b'\xed\x95\x9c\xea\xb8\x80'
utf-8로 인코딩 했을 때와는 다른 바이트 문자열이 출력되는 것을 확인할 수 있을 것이다.
디코딩 (decoding)
이번에는 반대로 인코딩된 바이트 문자열을 유니코드 문자열로 변환할 수 있는 디코딩에 대해서 알아보자. 다음 예제처럼 euc-kr로 인코딩된 바이트 문자열은 euc-kr로만 디코딩을 해야 한다.
>>> a = '한글'
>>> b = a.encode('euc-kr')
>>> b.decode('euc-kr')
'한글'
만약 euc-kr로 인코딩된 바이트 문자열을 utf-8로 디코딩하려고 한다면 어떻게 될까?
>>> b.decode('utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc7 in position 0: invalid continuation byte
잘못된 문자셋으로 디코딩 하려고 하면 위와 같은 오류가 발생하게 된다.
입출력과 인코딩
인코딩 관련해서 개발자들이 가장 고생하는 부분은 바로 데이터의 입출력 관련해서이다. 이것 역시 문자열과 인코딩에 대한 개념만 확실히 이해하면 어렵지 않지만 이것들에 대한 이해 없이 무작정 인코딩, 디코딩을 남발하다가는 다중 인코딩되거나 다중 디코딩되면서 문자열이 꼬여 버리는 불상사가 발생하기도 한다.
파일을 읽거나 네트워크를 통해 데이터를 입력받을 때 많은 사람들이 추천하는 방법은 다음과 같다.
- 입력으로 받은 바이트 문자열을 가능한한 가장 빨리 유니코드 문자열로 디코딩 할 것.
- 변환된 유니코드 문자열로만 함수나 클래스등에서 사용할 것.
- 입력에 대한 결과를 전송하는 마지막 부분에서만 유니코드 문자열을 인코딩해서 리턴할 것.
위와 같은 규칙을 준수한다면 인코딩 관련해서 큰 어려움이 없을 것이다.
다음은 euc-kr 문자셋으로 작성되어진 파일을 읽고 변경하여 저장하는 예제이다.
# 1. euc-kr로 작성된 파일 읽기
with open('euc_kr.txt', encoding='euc-kr') as f:
data = f.read() # 유니코드 문자열
# 2. unicode 문자열로 프로그램 수행하기
data = data + "\n" + "추가 문자열"
# 3. euc-kr로 수정된 문자열 저장하기
with open('euc_kr.txt', encoding='euc-kr', mode='w') as f:
f.write(data)
파일을 읽는 open 함수에는 encoding을 지정하여 파일을 읽을 수 있는 기능이 있다. 이 때 읽어들인 문자열은 유니코드 문자열이 된다. 마찬가지로 파일을 작성할 때도 encoding을 지정하여 파일을 작성할 수 있다. 만약 encoding항목을 생략하면 디폴트로 utf-8이 지정된다.
소스코드 인코딩
파이썬 shell 이 아닌 에디터로 파이썬 코딩을 할 경우 소스 코드의 인코딩은 매우 중요하다. 소스 코드의 인코딩이란 소스 코드 파일이 현재 어떤 방식으로 인코딩되었지를 의미한다.
우리가 위 예제에서 알아보았듯이 파일은 utf-8 인코딩으로 저장할 수도 있고 euc-kr로 저장할 수도 있었다. 소스 코드도 파일이므로 인코딩 타입이 반드시 존재한다. 파이썬은 소스코드의 인코딩을 명시하기 위해 소스 코드 제일 상단에 다음과 같은 문장을 넣어 주어야 한다.
# -*- coding: utf-8 -*-
만약 소스코드가 utf-8로 인코딩된 파일이라면 위와 같이 작성하면 되고 euc-kr로 인코딩된 경우라면 다음과 같이 작성해야 한다. (파이썬 3.0 부터는 utf-8이 디폴트이므로 utf-8로 인코딩된 소스 코드인 경우 위 문장을 생략 해도 된다.)
# -*- coding: euc-kr -*-
만약 소스코드는 euc-kr로 인코딩되었는데 파일상단에는 utf-8로 명시되어져 있다면 문자열 처리하는 부분에서 인코딩 관련한 오류가 발생할 것이다.
박응용 님 2553
2020년 7월 8일 10:56 오후