챕터 3. 문자열
“니가 내 친구라 알려주는거지만, 내 알파벳은 네 알파벳이 끝나는 곳에서 시작해!”
— Dr. Seuss, On Beyond Zebra!
뛰어들기 전에 알아야 할 몇가지 지루한 것들
부건빌 마을 사람들이 세상에서 가장 적은 수의 문자기호를 쓴다는 것을 아는가? 그들의 Rotokas alphabet은 단 12 문자로 구성된다: A, E, G, I, K, O, P, R, S, T, U, 그리고 V. 반면에 중국어나 일본어, 한국어같은 경우 몇 천 개나 되는 문자기호를 가지고 있다. 영어는, 물론, 26 문자를 가지며 — 대 소 문자를 구분할 경우 52개 — 그에 더해 !@#$%& 같은 유용한 문장 부호들을 가진다.
사람들이 “문서 데이터”에 대해 이야기 할 때, 그들은 “컴퓨터 화면에 뜨는 문자나 기호”를 생각한다. 그러나 컴퓨터는 문자나 기호를 다루지 못한다; 그들은 비트와 바이트를 다룬다. 당신이 컴퓨터 화면에서 본 적 있는 텍스트의 모든 조각들은 정확히는 특정 문자 부호화(character encoding)에 저장되어 있다. 간단히 설명하자면, 문자 부호화가 화면에 보이는 것들과, 실제 메모리나 디스크 상에 저장되어 있는 문자를 이어준다. 문자 부호화는 매우 여러 가지가 있는데, 몇몇은 러시아어나 중국어 또는 영어 각각에 한정되어 있고, 어떤 것들은 여러 언어를 지원한다.
실제로는, 그보다 더 복잡하다. 많은 문자들이 여러 부호화에 공통적으로 존재하지만, 각각의 부호화는 그 문자들을 디스크에 담는데 서로 다른 바이트 열을 사용할 것이다. 이제 문자 부호화를 해독의 열쇠라고 생각할 수 있을 것이다. 누군가 당신에게 바이트 열- 파일 또는 웹페이지, 무엇이든-을 보내고, 그것의 “문서 데이터”가 필요할 때, 어떤 문자 부호화가 쓰였고 그렇기에 바이트를 문자들로 바꾸기 위해 무엇을 사용해야 하는지 알아야 한다. 만약 잘못된 키가 사용되거나 전혀 사용하지 않는다면, 코드를 스스로 풀어야 하는 어려운 상황에 놓일 것이다. 잘못 될 가능성은 충분하고, 그럴 경우 영문 모를 문자들만 보여질 것이다.
생략기호(apostrophe, “ ‘ ”)가 있어야 할 곳에, 물음표 비슷한 이상한 문자가 있는 웹페이지를 본 적 있을 것이다. 그것은, 보통 그 페이지의 저자가 문자 부호화를 정확히 선언하지 않아, 브라우저의 추측을 통해, 기대한 문자와 기대하지 않은 문자가 섞인 결과가 나온 것이다. 그것이 영어에서는 성가신 정도지만, 다른 언어들에서는 완전히 읽을수 없는 결과가 나올 수도 있다.
세계의 주요 언어들에는 각각의 문자 부호화가 있다. 각 언어가 서로 다르고, (역사적으로) 메모리와 디스크의 저장공간이 비쌌기 때문에, 각각의 문자 부호화는 특정 언어에 최적화 되었다. 그래서, 각각의 부호화는 그 언어의 문자를 나타내기 위해 같은 숫자를 사용하였다. 예를 들어, 우리가 잘 아는 ASCII 부호화는, 영문자를 저장하기 위해 0부터 127까지의 수를 사용했다. (65는 대문자 “A”, 97은 소문자 “a”, 그 외.) 영어는 매우 간단한 글자이기 때문에 128보다 적은 수로도 완전히 전달된다. 2를 지수로 계산하면, 한 바이트의 8비트 중 7비트만을 사용한다.
프랑스어, 스페인어, 독일어 같은 서유럽어는 영어보다 많은 글자를 가진다. 좀 더 정확하게는, 스페인어의 ñ 문자 같이 발음 구별 기호를 포함한 글자들을 가지고 있다. 이 언어들에 가장 흔하게 쓰이는 부호화는 CP-1252이다(윈도우즈에서 많이 사용되어 “windows-1252″라고 불리기도 한다). CP-1252 부호화는 0-127 범위에서는 ASCII 부호화와 같지만, 128-255 범위는 ‘묵음기호를 가지는 n’(241)이라던지, ‘위에 점 두개를 가지는 u’(252), 등의 문자를 가진다. 하지만 이것 또한 단 바이트 부호화이다; 가장 큰 수가 255이고, 여전히 한 바이트 안에 들어간다.
중국어, 일본어, 한국어 같은 언어들은 문자수가 많아 다 바이트 문자 셑을 요구한다. 각 “문자”는 0-65535까지의 2바이트 크기의 수로 재현된다. 그러나, 다 바이트 부호화 간에는 서로 다른 단 바이트 부호화들과 같은 문제가 존재한다. 표현해야 할 문자의 수가 많아졌기에 문자를 표현하기 위한 범위만 넓어졌을 뿐이다.
서로 네트워크로 연결이 되어있지 않고, 혼자 “텍스트”를 작성하고 인쇄 할 때는 큰 문제가 없었다. “평문”은 많지 않았다. 소스코드는 ASCII였고, 모든 사람들이 특정 워드 프로세서(풍부한 스타일링과, 문자 부호화를 추적하는 독자 포맷을 정의해놓은)를 사용하는 등. 사람들은 이런 문서를 원저자와 같은 워드 프로세스를 사용하여 읽었고, 모든 것은 문제가 없었다.
이제 이메일과 웹같은 세계적 네트워크의 성장에 대해 생각해 보자. 많은 “평문”들이 세계를 떠돌고, 한 컴퓨터에서 제작되어, 다른 컴퓨터로 전해지고, 다시 다른 컴퓨터에서 받아 보게된다. 컴퓨터는 수만 읽을 수 있지만, 그 수는 여러가지 의미를 지닐 수 있다. 악! 어떻게 해야 할까? 시스템은 “평문”의 모든 조각에 부호화 정보를 함께 보내도록 디자인 되어야 한다. 이것이 컴퓨터가 읽을 수 있는 수에서 인간이 읽을 수 있는 문자로의 전환을 위한 열쇠라는 것을 명심하자. 열쇠를 잃어버리면 왜곡된 텍스트, 횡설수설, 또는 더 안 좋은 결과를 낳는다.
당신이 지금까지 받은 모든 이메일을 하나의 데이터베이스 테이블에 저장하는 것처럼, 한 장소에 여러 조각의 문서를 저장하는 상황을 생각해 보자. 그것들을 잘 보려면 텍스트와 함께 문자 부호화를 저장해야만 한다. 어려운가? 이메일 데이터베이스를 검색하는 것은, 날아온 여러 부호화 사이의 변환을 의미한다. 흥미롭지 않은가?
이제 한 문서에 여러 언어의 문자가 같이 존재하는, 다언어 문서의 가능성에 대해 생각해보자. (힌트: 이런 프로그램들은 대개 “모드”를 바꾸기 위해 코드를 뛰어 넘는다. 휙, 지금은 Russian koi8-r 모드, 지금 241은 이 문자를 뜻해; 휙, 지금은 Mac Greek 모드네, 그럼 241이 다른 어떤 문자를 가리키지.) 물론 당신은 그런 문서들을 찾길 원할 것이다.
슬프게도, 당신이 문자에 대해 안다고 생각한 모든 것은 틀렸다, 사실 “평문”이란것은 존재하지 않기 때문이다.
3.2. 유니코드
유니코드는 모든 언어의 모든 문자를 표현하도록 디자인되었다. 유니코드는 4바이트 크기의 수로 각 문자, 자소나, 기호를 표현한다. 각 수는 세상에 존재하는 언어중 적어도 하나에는 있는 고유의 문자를 나타낸다. (모든 수가 다 쓰이진 않는다, 하지만 65535개 이상의 문자가 있기때문에, 2바이트로는 충분하지 않다.) 여러 언어에 쓰이는 문자는 특별히 어원상의 이유가 있지 않은 한 같은 수를 가진다. 정확히 문자만큼의 수, 수만큼의 문자. 모든 수는 언제나 하나의 문자 만을 의미한다. 거기에 생각해야 할 “모드”같은 건 없다. 당신이 쓰는 언어가 ‘A’를 가지고 있지 않아도 U+0041 은 언제나 ‘A’이다.
언뜻보면, 이것은 매우 좋은 아이디어 같아보인다. 하나의 인코딩이 모든 것을 관리한다. 한 문서 안의 여러 언어. 중간중간 인코딩을 바꾸기 위한 “모드 변환”은 더이상 존재하지 않는다. 하지만 바로 의문이 생긴다. 4바이트? 한 문자에 4바이트?! 그것은 영어나 스페인어 같은, 모든 문자를 1바이트만으로 표현 가능한 언어에는 끔찍히 낭비적이다. 사실, 그건 2바이트로 표현가능한 표의문자언어(중국어 등)의 경우도 마찬가지다.
1문자에 4바이트를 사용하는 유니코드가 있다. 이건 UTF-32라고 불리는데, 32비트는 4바이트이기 때문이다. UTF-32는 직관적 인코딩이다. 이건 각 유니코드 문자(4바이트 수)를 받아 같은 번호의 문자를 재현한다. 이것은 몇가지 장점을 가지는데, 가장 중요한 것은 언제나 문자열 안의 N번째 문자를 찾을수 있다는 것이다. 왜냐하면 N번째 문자는 언제나 4*N번째 바이트에서 시작하기 때문이다. 이것은 또한 몇가지 단점을 가진다. 가장 분명한 것은 문자마다 4바이트를 사용한다는 것이다.
많은 유니코드 문자가 있지만, 대부분의 사람들은 첫 65535개 이상 사용할 일이 없다. 그래서, UTF-16(16비트는 2바이트 이기에)이라 불리는 또 하나의 유니코드 인코딩이 존재한다. UTF-16은 0과 65535사이의 모든 문자를 부호화하고, 65535를 넘어가는, 잘 사용하지 않는 “astral plane”이 필요하다면 약간의 조작을 사용한다. 가장 분명한 장점 : UTF-16은 4바이트 대신 2바이트 만을 사용하기에 UTF-32보다 두배나 공간절약적이다. (except for the ones that don’t). 그리고 문자열에 특별히 astral plane 문자가 없다는 것만 확신(그렇지 않은 순간이 오기 전까지는 훌륭한 추정이라 할수 있다-_-)한다면, 여전히 문자열안의 N번째 문자를 쉽게 찾을수 있다.
하지만 UTF-32과 UTF-16에는 또한 몇가지 애매한 단점들이 있다. 서로 다른 컴퓨터 시스템은 각기 다른 방식으로 각 바이트를 저장한다. 그렇다는 것은 UTF-16에서 문자 U+4E2D는 지금 시스템이 빅 엔디안인지 리틀 엔디안인지에 따라 4E 2D또는 2D 4E로 저장될수 있다는 것이다. (UTF-32의 경우 바이트 저장순서를 결정하는데 더 많은 가능한 방식들이 있다.) 당신의 문서가 당신 컴퓨터를 떠나지 않는 동안은 안전하다. — 한 컴퓨터의 서로 다른 프로그램은 바이트 저장 순서가 같다. 하지만 당신이 월드 와이드 웹을 통해서나 다른 방식으로 시스템간 문서를 전송하고 싶다고 생각하는 순간, 어떤 방식으로 바이트를 저장했는지 나타내는 것이 필요하다. 그렇지 않으면, 데이터를 받은 시스템은 2바이트 크기의 시퀀스 4E 2D가 U+4E2D를 나타내는 것인지 U+2D4E를 나타내는 것인지 알 수가 없다.
이런 문제를 풀기위해, 다 바이트 유니코드 인코딩은 문서의 시작부분에, 출력되지 않는 특수한 문자 “바이트 순서 표식”를 사용한다. UTF-16을 위한 바이트 순서 표식는 U+FEFF이다. 만약 FF FE로 시작되는 UTF-16 문서를 받는다면 바이트 순서가 바르다는 것을 알 수 있다. ; 만약 그것이 FE FF로 시작한다면 바이트 순서가 뒤바뀐 것을 알 수 있다.
여전히, UTF-16도 이상적이진 않다. 당신이 ASCII 문자를 자주 다룬다면 특히 그렇다. 중국의 웹 페이지 조차도 ASCII문자를 많이 사용한다는 것을 생각해 보자 — all the elements and attributes surrounding the printable Chinese characters. 항상 N번째 문자를 찾을수 있다는 것은 좋다. 하지만 여전히 astral plane 문자에 대한 문제가 남아있고, 그것은 모든 문자가 완벽하게 2바이트씩이라는 것을 보장할수 없다는 뜻이다. 그렇기 때문에 특별한 인덱스를 포함시키지 않는 한, 언제나 진정으로 N번째 문자를 찾을수 있는 것이 아니다. 그리고, 확실한 것은 세상에 ASCII 문자가 정말 많다는 것이다.
어떤 사람들이 이 문제에 대해 고민했고, 결국 해결법을 들고 나왔다 :
UTF-8
UTF-8은 유니코드를 위한 가변적 인코딩 시스템이다. 각각의 문자는 서로 다른 바이트 수를 가진다는 것이다. ASCII문자에는 UTF-8은 문자당 한바이트만 사용한다. 사실, 그것은 정확하게 같은 바이트를 사용한다; UTF-8에서 첫 128개(0-127)의 문자는 ASCII인코딩과 같다. ñ과 ö같은 “확장된 라틴”문자는 2바이트를 사용한다. (바이트 사용이 UTF-16과 같지 않기에 가끔 심각한 비트꼬임같은 문제가 일어날 수 있다.) 中같은 중국어는 3바이트를 사용한다. 거의 사용하지 않는 “astral plain” 문자는 4바이트를 사용한다.
단점 : 각각의 문자가 서로 다른 바이트의 수를 사용하기 때문에, N번째 문자를 찾기 위해 O(N) 명령어를 사용해야 한다. — 문자열이 길어질수록 특정 문자를 찾는데 시간이 더 걸린다. 또한, 문자를 바이트로 바꿀때나 바이트를 문자로 풀어낼때 비트꼬임 현상이 발생할 수 있다.
장점 : ASCII문자를 위한 매우 효율적인 인코딩이다. 확장 라틴 문자를 사용하기에 UTF_16보다 낫고, 중국어 사용에는 UTF-32보다 낫다. 또한 비트 꼬임 현상이 발생하기 전까지 바이트 순서에 대한 문제도 없다. UTF-8로 작성된 문서는 어떤 컴퓨터에서도 같은 바이트 순서를 사용한다.
3.3. 뛰어들기
파이썬 3에서, 모든 문자열운 유니코드 문자로 이루어진다. “UTF-8또는 CP-1252로 인코딩 된 파이썬 문자열” 같은 것은 없다. 여기에 UTF-8이 사용되었어? 하는 질문은 맞지 않다. UTF-8은 문자들을 바이트 들로 바꾸는 방법이다. 만약 당신이 문자열을 받아 특정한 인코딩을 사용하여 바이트열로 바꾸길 바란다면, 파이썬이 도울 수 있다. 만약 당신이 바이트열을 가지고 문자열로 바꾸길 원한다면, 역시 파이썬이 도울 수 있다. 바이트는 문자가 아니다; 바이트는 바이트이다. 문자는 추상적 개념이다. 문자열은 그런 추상의 모음이다.
>>> s = ‘深入 Python’
>>> len(s)
9
>>> s[0]
‘深’
>>> s + ‘ 3′
‘深入 Python 3′
1. 문자열을 만들기 위해 따옴표로 양끝을 닫아주자. 파이썬 문자열은 홑따옴표나 겹따옴표 둘중 하나로 정의 될수 있다.
2. len() 함수는 문자열의 길이를 반환한다. 예로 문자의 수. 이것은 리스트의 길이를 측정할때도 사용된다. 문자열은 문자들의 리스트라고 생각하자.
3. 리스트에서 각 항목을 꺼내는 것 처럼, 색인을 사용하여 문자열의 개별 문자를 얻어낼 수 있다.
4. 리스트와 같이, + 연산자를 사용하여 문자열을 붙일 수 있다.
3.4 문자열 서식
SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], ①
1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
def approximate_size(size, a_kilobyte_is_1024_bytes=True):
'''Convert a file size to human-readable form. ②
Keyword arguments:
size -- file size in bytes
a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
if False, use multiples of 1000
Returns: string
''' ③
if size < 0:
raise ValueError('number must be non-negative') ④
multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
for suffix in SUFFIXES[multiple]:
size /= multiple
if size < multiple:
return '{0:.1f} {1}'.format(size, suffix) ⑤
raise ValueError('number too large')
1. ‘KB’, ‘MB’, ‘GB’ 등 등 은 모두 문자열이다.
2. 참조문은 문자열이다. 이 참조문은 여러줄에 걸쳐있기 때문에 문자열의 처음과 끝에 세따옴표를 사용한다.
3. 이(두번째) 세따옴표는 참조문을 닫는다.
4. 이 문자열은 예외를 인간이 읽을수 있는 에러 메세지로 변환하는데 사용되었다.
5. 이건… 와, 이 건 도대체 뭔가?
파이썬 3는 문자열 내 서식 사용을 지원한다. 물론 매우 복잡한 식도 가능하지만, 하나의 위치 지정자에 하나의 값을 넣는게 가장 기본적인 사용법이다.
username = 'mark' password = 'PapayaWhip' ① "{0}'s password is {1}".format(username, password) ② "mark's password is PapayaWhip"
1. 아니, 내 암호는 PapayaWhip이 아니다.
2. 여기에는 많은 것이 담겨있다. 우선, 실제 문자열(string literal)을 부르는 메소드가 사용되었다. 문자열은 객체이고, 객체는 메소드를 가진다. 둘째, 이 전체식은 하나의 문자열을 출력한다. 셋째, {0}, {1}은 format()속성에 따라나오는 인자들로 대체될 공간이다.
3.4.1. 혼합 필드명
앞의 예제는 가장 단순한 정수형 치환필드가 사용된 경우를 보여주고 있다. 정수 치환필드는 format() 메소드의 인수 리스트가 들어갈 위치 색인으로 취급된다. {0}은 첫번째 인수(이 경우 username)로 대체되고, {1}은 두번째 인수(password)로 대체된다는 뜻이다. 원하는 만큼의 인수, 인수 만큼의 위치 색인을 사용할 수 있다. 그러나 사실 치환필드는 그것보다 훨씬 더 강력하다.
>>> import humansize >>> si_suffixes = humansize.SUFFIXES[1000] ① >>> si_suffixes ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] >>> '1000{0[0]} = 1{0[1]}'.format(si_suffixes) ② '1000KB = 1MB'
1. humansize 모듈의 함수를 호출하는 대신, 모듈에 정의된 자료 구조 중 하나(1000의 제곱수 “SI” 의 단위 리스트)를 가져온다.
2. 이것은 복잡해 보이지만, 그렇지 않다. {0}은 format() 속성에 전달된 첫 인자, si_suffixes를 참조한다. 그러나 si_suffixes는 리스트이다. 그래서 {0[0]}은 format() 속성에 전달된 첫번째 인자-리스트의 첫번째 항목을 참조한다: ‘KB’. 한편, {0[1]} 은 같은 리스트의 두번째 항목을 참조한다: ‘MB’. 중괄호 밖의 모든것 — 1000, = 표시, 빈칸을 포함해 — 은 건드리지 않는다. 최종결과물은 문자열 ’1000KB = 1MB’이 된다.
이 예제는 형식지정자가 파이썬 문법을 이용하여 데이터 구조의 항목이나 속성 들에 접근할수 있다는 것을 보여준다. 다음 혼합치환필드의 이름은 “잘 작동됨”이다.
- 리스트에서는, 색인을 통해 항목에 접근한다(앞의 예제와 같이)
- 사전에서는, 키를 가지고 값에 접근한다.
- 모듈에서는, 이름으로 함수와 변수에 접근한다.
- 클래스 인스턴스에서는, 이름을 통해 속성과 메소드에 접근한다
- 위의 어떤 것도 함께 사용 가능하다.
위의 모든 것을 섞은, 재밌는 예제가 여기 있다:
>>> import humansize
>>> import sys
>>> '1MB = 1000{0.modules[humansize].SUFFIXES[1000][0]}'.format(sys)
'1MB = 1000KB'
이 코드는 이렇게 작동한다:
- sys모듈은 현재 실행되고 있는 파이썬 인스턴스의 정보를 담고 있다. 이 모듈을 한번 불러온 이후에는 format()메소드에 sys 모듈 자체를 인수로 넘길 수 있다. 그렇기 때문에 치환필드 {0}은 sys모듈을 참조한다.
- sys.modules는 이 파이썬 인스턴스로 불러온 모든 모듈에 대한 사전이다. 키는 문자열로 된 모듈 이름이다; 값은 모듈 객체 그 자신이다. 그래서 {0.modules}는 불러온 모듈에 대한 사전을 참조한다.
- sys.modules['humansize']는 방금 불러온 humansize 모듈이다. 치환필드 {0.modules[humansize]}는 humansize 모듈을 참조한다. 이 곳의 문법이 약간 다르다는 것에 주의하자. 실제 파이썬 코드에서, sys.modules 사전의 키는 문자열이다; 그들을 참조하기 위해서는 모듈 이름 주위로 따옴표를 넣어주어야 한다.(‘humansize’ 같이) 그러나 치환필드 안에서는, 사전 키 이름 주위에 따옴표를 쓰지 않는다.(humansize 같이)
- sys.modules['humansize'].SUFFIXES는 humansize모듈 상단에 정의된 사전이다. 치환필드 {0.modules[humansize].SUFFIXES}는 그 사전을 참조한다.
- sys.moduels['humansize'].SUFFIXES[1000]은 SI 단위 접미사의 리스트이다: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']. 치환필드 {0.modules[humansize].SUFFIXES[1000]}은 그 리스트를 참조한다.
- sys.modules['humansize'].SUFFIXES[1000][0]은 SI 단위 접미사 리스트의 첫 항목이다: ‘KB’. 그래서, 결욱 치환필드 {0.modules[humansize].SUFFIXES[1000]}는 2문자짜리 문자열 KB로 치환된다.
3.4.2. 형식 지정자
하지만 잠깐! 여기서 끝이 아니다! humansize.py에서 가져온 코드의 낯선 부분을 다시 보자.
if size < multiple:
return '{0:.1f} {1}'.format(size, suffix)
{1}은 format() 메소드에 넘겨진 두번째 인수, suffix로 치환된다. 하지만 {0:.1f}는 뭔가? 이것은 이미 알고 있는 {0}과 아직 알지 못하는 :.1f로 나뉜다. (콜론을 포함한) 두번째 절반을 형식 지정자로 규정한다. 형식 지정자는 치환된 변수를 필요한 형식으로 정돈한다.
- C의 printf() 처럼, 형식지정자는 치환된 텍스트를 여러가지 유용한 방식으로 수정?(munge)할 수 있게 해준다. 0을 추가하거나 여백을 넣거나 문자열을 정렬할수도, 소수 정밀도를 조절하거나 심지어는 수를 16진수로 변환할 수도 있다.
치환필드에서, 콜론 (:)은 형식 지정자의 시작을 표시한다. 형식지정자 “.1”은 “소수점 이하 1자리만 보이도록 반올림 하라”는 뜻이다. 형식 지정자 “f”는 “고정 소수점 수”(지수 표현법이나 다른 소수 표현법과는 반대로)라는 뜻이다. 그러므로, 698.25라는 크기(size)와 ‘GB’라는 접미사(suffix)가 주어지면, 변환된 문자열(formatted string)은 ’698.3GB’가 될 것이다. 왜냐하면 698.25가 소수점 한자리로 반올림 되고, 그 이후에 접미사가 숫자 뒤에 붙기 때문이다.
>>> '{0:.1f} {1}'.format(698.25, 'GB')
'698.3 GB'
형식 지정자에 대한 세부적인 기술은, 공식 파이썬 문서 Format Specification Mini-Language를 참고하자.
3.5. 그 외의 일반적인 문자 메소드
문자열은 형식 지정 이외의 다른 유용한 트릭도 사용할 수 있다.
>>> s = '''Finished files are the re- ① ... sult of years of scientif- ... ic study combined with the ... experience of years.''' >>> s.splitlines() ② ['Finished files are the re-', 'sult of years of scientif-', 'ic study combined with the', 'experience of years.'] >>> print(s.lower()) ③ finished files are the re- sult of years of scientif- ic study combined with the experience of years. >>> s.lower().count('f') ④ 6
1. 대화형 파이썬 쉘에서 여러줄 문자열을 입력할 수 있다. 세 따옴표를 이용하여 여러줄 문자열을 시작하면, 엔터를 치면 쉘은 당신이 문자열을 계속 입력하기를 기다릴 것이다. 마지막에 세 따옴표를 사용해 문자열을 끝내고 난 후의 엔터는 명령을 실행할 것이다.(이 경우에는 문자열을 s에 할당한다.)
2. splitlines() 메소드는 하나의 여러줄 문자열을 받아 문자열들의 리스트를 반환한다. 원본 문자열의 각 줄이 한 항목이 된다. 각 열 마지막에 줄내림문자가 빠져있는 것에 주의하자.
3. lower() 메소드는 문자열 전체를 소문자로 변환시킨다. (비슷하게, upper() 메소드는 문자열을 대문자로 변환시킨다.)
4. count() 메소드는 하위문자열의 발생 횟수를 센다. 그렇다, 저 문장에는 정말 여섯개의 “f”가 있다.
또한 이런 식으로도 자주 사용한다. 당신에게 key1=value1&key2=value2형태의 키-값 리스트가 있고, 그것들을 나누어 {key1: value1, key2: value2} 형태의 사전으로 만든다고 해보자.
>>> query = 'user=pilgrim&database=master&password=PapayaWhip' >>> a_list = query.split('&') ① >>> a_list ['user=pilgrim', 'database=master', 'password=PapayaWhip'] >>> a_list_of_lists = [v.split('=', 1) for v in a_list] ② >>> a_list_of_lists [['user', 'pilgrim'], ['database', 'master'], ['password', 'PapayaWhip']] >>> a_dict = dict(a_list_of_lists) ③ >>> a_dict {'password': 'PapayaWhip', 'user': 'pilgrim', 'database': 'master'}
1. split() 문자열 메소드는 하나의 인수(구획문자)를 받아, 구획문자를 기준으로 문자열을 문자열들의 리스트로 분리시킨다. 여기에서 구획문자는 &(ampersand)이지만, 그 외 어떤 것이든 올 수 있다.
2. 이제 문자열들의 리스트가 생겼다. 각각의 문자열은 키가 나오고 그 후에 등호 표시가 나오고, 그 후에 값이 나오는 형태이다. 우리는 리스트 전체에 반복적으로 각 문자열을 첫번째 등호 표시를 기준으로 두 문자열로 나누려고 한다. 만약 우리가 ‘key=value=foo’.split(‘=’)라고 쓴다면, 우리는 세 항목짜리 리스트 ['key', 'value', 'foo']를 얻게 될것이다.
3. 마침내, 파이썬이 dict()함수에 리스트들의 리스트를 넘김으로서 간단히 사전으로 변환시킬수 있게 되었다.
3.6. 문자열 VS. 바이트
바이트는 바이트이다; 문자는 추상이다. 변경 불가능한 유니코드 문자의 모음을 문자열이라고 부른다. 변경 불가능한 0에서 255사이의 수를 바이트 객체라고 부른다.
>>> by = b'abcd\x65' ① >>> by b'abcde' >>> type(by) ② <class 'bytes'> >>> len(by) ③ 5 >>> by += b'\xff' ④ >>> by b'abcde\xff' >>> len(by) ⑤ 6 >>> by[0] ⑥ 97 >>> by[0] = 102 ⑦ Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'bytes' object does not support item assignment
bytes 객체를 정의하기 위해, b’ ‘ “바이트 리터럴” 문법이 사용된다. 바이트 상수안의 각 바이트는 ASCII 문자 또는 \x00 에서 \xff 까지의 부호화된 16진수 수가 될 수 있다.
2. bytes 객체의 형식은 bytes이다.
3. 리스트나 문자열처럼, len() 함수를 사용해 bytes 객체의 길이를 얻을 수 있다.
4. 리스트나 문자열처럼, + 연산자를 사용해서 bytes 객체를 연결할 수 있다. 결과는 새 bytes 객체가 된다.
5. 5바이트의 bytes 객체와 1바이트의 bytes 객체를 연결하면 6바이트의 bytes 객체를 얻는다.
6. 리스트나 문자열처럼, 색인을 이용해 bytes 객체의 개별 바이트를 얻을 수 있다. 문자열의 각 항목은 문자이다; bytes 객체의 각 항목은 숫자이다. 정확히는 0에서 255사이의 숫자이다.
7. bytes객체는 변경 불가능하다; 개개의 바이트에 값을 할당할 수는 없다. 개개의 바이트를 바꿀 필요가 있다면, 슬라이스 메소드(문자열과 같은 방식으로 작동한다)와 연결 열산자(역시 문자열과 같은 방식이다)를 사용하거나,bytes 객체를 bytearray 객체로 변환시킬 수 있다.
>>> by = b'abcd\x65' >>> barr = bytearray(by) ① >>> barr bytearray(b'abcde') >>> len(barr) ② 5 >>> barr[0] = 102 ③ >>> barr bytearray(b'fbcde')
1. bytes 객체를 bytearray 객체로 변경하기 위해, bytearray() 함수를 사용한다.
2. bytes 객체의 모든 메소드와 연산자는 bytearray 객체에서도 사용가능하다.
3. 다른 점이 하나 있다면, bytearray 객체는 색인을 통해 개별 바이트를 할당할 수 있다는 것이다. 할당값은 반드시 0에서 255사이의 정수여야 한다.
바이트와 문자열을 섞는 것은 불가능하다.
>>> by = b'd' >>> s = 'abcde' >>> by + s ① Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't concat bytes to str >>> s.count(by) ② Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't convert 'bytes' object to str implicitly >>> s.count(by.decode('ascii')) ③ 1
1. 바이트와 문자열은 연결할 수 없다. 그들은 서로 다른 두 데이터 형이다.
2. 문자열에서 바이트의 발생횟수를 셀 수 없다. 문자열은 문자의 집합이다. 아마도 어떤이는 “특정 문자 인코딩의 바이트열을 해독한 후 그 문자열의 발생횟수를 세면 되지 않나요?”라고 물어올 것이다. 그렇다면, 명시적 선언이 필요하다. 파이썬 3는 바이트와 문자열간의 암묵적 변환을 허용하지 않는다.
3. 놀라운 우연의 일치로, 이 줄이 “특정 문자 인코딩의 바이트열을 해독한 후 그 문자열의 발생횟수를 세면 되지 않나요?”이다.
이것이 문자열과 바이트간의 연결점이다. bytes 객체는 문자 인코딩을 받아 문자열을 반환하는 decode() 메소드를 가지고 있다. 그리고 문자열은 문자 인코딩을 받아 bytes 객체를 반환하는 encode() 메소드를 가진다. 이전 예제의 해독과정은 비교적 직관적이다. — 바이트열을 ASCII 인코딩을 통하여 문자열로 변환한다. 하지만 문자열을 지원하는 (비-유니코드 인코딩을 포함한) 어떤 인코딩에서도 과정은 같다.
>>> a_string = '深入 Python' ① >>> len(a_string) 9 >>> by = a_string.encode('utf-8') ② >>> by b'\xe6\xb7\xb1\xe5\x85\xa5 Python' >>> len(by) 13 >>> by = a_string.encode('gb18030') ③ >>> by b'\xc9\xee\xc8\xeb Python' >>> len(by) 11 >>> by = a_string.encode('big5') ④ >>> by b'\xb2`\xa4J Python' >>> len(by) 11 >>> roundtrip = by.decode('big5') ⑤ >>> roundtrip '深入 Python' >>> a_string == roundtrip True
1. 이것은 문자열이다. 이것은 9개의 문자를 가진다.
2. 이것은 bytes 객체이다. 이것은 13바이트이다. 이것은 a_string을 UTF-8로 인코딩했을때 얻을 수 있다.
3. 이것은 bytes 객체이다. 이것은 11바이트이다. 이것은 a_string을 GB18030으로 인코딩했을 때 얻을 수 있다.
4. 이것은 bytes 객체이다. 이것은 11바이트이다. 이것은 a_string을 Big5로 인코딩했을 때 얻을수 있는 완전히 다른 종류의 바이트 열이다.
5. 이것은 문자열이다. 이것은 9개의 문자를 가진다. 이것은 by를 가져와 Big5 인코딩 알고리즘으로 해독했을때 얻을 수 있다. 이것은 원본 문자열과 완전히 같다.
3.7. 추신: 파이썬 소스코드의 문자 부호화
파이썬 3는 당신의 소스코드가 — 예를 들어 각 .py 파일 — 이 UTF-8로 인코딩 된것으로 추정한다.
- 파이썬 2에서는, .py 파일을 위한 기본 인코딩이 ASCII이다. 파이썬 3에서는 기본 인코딩이 UTF-8이다.
만약 당신의 파이썬 코드에 다른 인코딩을 사용하고자 한다면, 각 파일의 첫번째 줄에 인코딩을 선언할 수 있다. 이 선언은 .py 파일을 windows-1252로 정의한다:
# -*- coding: windows-1252 -*-
만약 첫번째 줄이 UNIX형 hash-bang(?) 명령이라면, 문자 인코딩 변환은 두번째 줄에 올수도 있다.
#!/usr/bin/python3
# -*- coding: windows-1252 -*-
더 많은 정보를 위해서는 PEP 263: Defining Python Source Code Encodings을 참조하라.
3.8. 더 읽어볼 문서
파이썬에서의 유니코드:
일반적인 유니코드:
- The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
- On the Goodness of Unicode
- On Character Strings
- Characters vs. Bytes
문자열과 문자열 형식화: