CodeOnWeb
로그인

챕터 4. 문자열(Strings)

파이썬의 문자열에 대해 알아봅니다.

Park Jonghyun 2015/09/01, 23:20

내용

파이썬 3에 뛰어들기 (22)

챕터 -1. “파이썬 3로 뛰어들기”에서 달라진 점 챕터 0. 파이썬 설치하기 챕터 1. 첫 파이썬 프로그램 챕터 2. 고유 자료형 챕터 3. 컴프리헨션(Comprehensions) 챕터 4. 문자열(Strings) 챕터 5. 정규표현식(Regular Expressions) 챕터 6. 클로저와 제너레이터(Closures & Generators) 챕터 7. 클래스와 반복자(Classes & Iterators) 챕터 8. 고급 반복자(Advanced Iterators) 챕터 9. 단위 테스트(Unit Testing) 챕터 10. 리팩토링(Refactoring) 챕터 11. 파일 챕터 12. XML 챕터 13. 파이썬 객체 직렬화 챕터 14. HTTP 웹서비스 챕터 15. 사례 연구: chardet을 파이썬 3로 이식하기 챕터 16. 파이썬 라이브러리 패키징하기 부록 A. 2to3를 이용해서 코드를 파이썬 3로 이식하기 부록 B. 특수 함수 이름 부록 C. 이제 어디로 가야 할까요? 부록 D. 문제 해결

"친구니까 말해 주는거야.
내 알파벳은 네가 알고 있는 알파벳이 끝나는 데서 시작해!"
닥터 수스, "Z"ebra들 넘어서!

약간 지루하지만 반드시 짚고 넘어가야 할 것들

많은 사람들이 무심코 지나치는 사실이지만, 문자 체계는 놀랄 정도로 복잡합니다. 알파벳을 예로 들어봅시다. Bougainville이라는 나라는 세상에서 가장 적은 수의 알파벳을 사용하는 나라입니다. 그 문자의 이름은 Rotokas alphabet인데 단지 12개의 문자(A, E, G, I, K, O, P, R, S, T, U, V)만으로 구성되어 있습니다. 이와는 반대로, 중국어나 일본어, 한국어 같은 언어는 수 천여개 이상의 알파벳에 해당하는 문자를 가지고 있습니다. 영어는 물론 26개의 문자(대소문자를 구별한다면 52개)와 !@#$%& 같은 약간의 특수문자도 가지고 있지요.

여러분은 "텍스트"라고 하면 컴퓨터 화면 위의 문자나 기호를 떠올릴지도 모르겠습니다. 하지만 컴퓨터는 문자나 기호를 다루지 못합니다. 대신 비트나 바이트를 다룰 수 있지요. 여러분이 모니터에서 보는 텍스트는 사실 특정 문자 인코딩(character encoding)에 의해 저장된 비트나 바이트인 셈입니다. 아주 대략적으로 말하자면, 문자 인코딩은 여러분이 화면을 통해 보는 것과 컴퓨터의 메모리나 디스크에 실제로 저장되는 것 사이의 연결고리라고 할 수 있습니다. 문자 인코딩의 종류는 아주 다양합니다. 러시아어나 중국어, 영어와 같이 특정 언어에 최적화된 것에서부터 여러 가지 언어를 한꺼번에 처리할 수 있는 종류도 있습니다.

문자열 인코딩의 세계는 사실 방금 전의 설명보다 조금 더 복잡합니다. 어떤 인코딩을 쓰던지 잘 표시되는 있는 문자(예를 들어 알파벳 문자)라 하더라도, 그 문자가 디스크에 저장될 때는 각 인코딩 마다 서로 다른 바이트 코드를 이용할 수도 있습니다. 따라서 문자 인코딩은 암호를 푸는 열쇠 정도로 생각할 수 있습니다. 어떤 사람이 여러분에에 바이트 배열(파일, 웹페이지, 뭐든지요)을 주면서 이건 텍스트라고 주장했다 칩시다. 그 바이트 배열을 문자로 해석하기 위해서는 그 배열을 저장할 때 어떤 문자 인코딩을 썼는지 알아야 합니다. 파일을 준 사람이 인코딩을 엉터리로 알려주거나 아예 알려주지 않았다면, 여러분은 스스로 그 코드를 크래킹해야 할 것입니다. 그리고 아마 십중팔구 실패하겠지요.

따옴표가 나와야 할 자리에 요상하게 생긴 물음표가 나오는 웹페이지를 본 적이 있나요? 아니면 웩켁쵁 처럼 희한한 문자로 가득한 웹페이지는요? 이는 웹 페이지 작성자가 문자 인코딩을 제대로 설정 해주지 않아서 발생하는 일입니다. 웹 브라우저가 나름대로 추측해서 처리 하겠지만, 추측이 맞지 않으면 이런 요상한 페이지를 출력하게 됩니다. 영어의 경우엔 좀 성가실 뿐 대강의 내용은 파악할 수 있겠지만, 다른 언어에서는 완전히 읽을 수 없는 결과가 나오기도 합니다.

세계 대부분의 주요 언어는 고유한 문자 인코딩을 가지고 있습니다. 각 나라의 문자가 서로 다르고 이를 한꺼번에 처리하는 것은 컴퓨터 자원의 낭비였기에, 각 나라의 문자 인코딩 방식은 그 나라의 언어에 최적화되어 있습니다. 무슨 말이냐 하면, 각 인코딩은 0-255 사이의 숫자를 이용해 그 나라의 문자를 표현한다는 것입니다. 예를 들어, ascii 인코딩(들어보신 분이 있을겁니다)에서는 영어의 각 문자와 기호를 0-127 사이의 숫자로 표현합니다. (65는 대문자 "A"를, 97은 소문자 "a"를 표현하는 식이죠.) 영어는 적은 수의 알파벳을 가지고 있으므로, 128 이하의 숫자로 모든 문자를 표현할 수 있습니다. 128개는 2의 7승이므로, 한 바이트(2의 8승)로 알파벳을 모두 표현할 수 있는 셈입니다.

프랑스어, 스페인어, 독일어 같은 서유럽권 언어는 영어보다 많은 수의 알파벳을 가지고 있습니다. 좀 더 정확히 말하면, 이들 언어에는 특별한 지시자와 결합한 문자가 있습니다. 스페인어의 ñ 같은 문자 말이지요. CP-1252는 이들 언어를 위한 대표적인 인코딩 방식입니다. MS 윈도우즈에서 많이 사용되었기 때문에 "windows-1252"라고도 부르지요. CP-1252 인코딩은 0-127 사이의 코드가 ascii의 코드값과 동일합니다. 128-255 까지의 숫자에는 앞에서 예를 들었던 ñ과 같은 확장 문자를 할당해 두었습니다. 예를 들어, ñ는 241이고, ü는 251입니다. ascii 인코딩 보다는 많은 코드가 필요하지만, 그래도 여전히 한 바이트(0-255)만으로 모든 문자를 표현할 수 있습니다.

한편 중국어, 일본어, 한국어 같은 언어는 한 바이트로 표현하기에 너무 많은 문자를 가지고 있습니다. 그래서 문자 하나를 두 개의 바이트(0-65535)로 표현합니다. 바이트 수가 하나 늘어나기는 했지만, 인코딩 별로 같은 코드를 다른 문자에 대응하는 문제는 여전히 남아있습니다. 표현해야 하는 문자의 수가 증가한 것 말고는 차이가 없습니다.

이런 문제는 사람들이 직접 "텍스트"를 입력하고 출력하는 네트워크 없던 시절에는 별 문제가 아니었습니다. 사실 "평범한 텍스트(plain text)"라는 개념은 존재하지도 않았습니다. 소스 코드는 ascii 코드로 되어 있고, 워드프로세서를 사용하는 사람들은 평범한 텍스트건 워드프로세서에서 따로 정의한 기호이던 간에, 같은 소프트웨어를 사용하는 한 별 문제가 없었습니다.

이제 이메일이나 웹 서핑같은 일이 일상이 되어버린 글로벌 네트워크(global network)의 현재를 생각해보죠. 엄청난 양의 "평범한 텍스트"가 전세계를 휘저으며 돌아 다니고 있습니다. 한 컴퓨터에서 작성된 텍스트가 두 번째 컴퓨터를 통해 중계되고 세 번째 컴퓨터에 전달된 후 화면에 출력됩니다. 컴퓨터는 숫자만 다룰 수 있는데, 한 숫자가 의미하는 문자가 인코딩에 따라 다를 수 있습니다. 그럼 어떻게 해야 될까요? "평범한 텍스트" 하나 하나를 전송할 때 마다 인코딩 정보도 함께 전송되도록 해야 합니다. 명심하세요. 인코딩은 컴퓨터에서 처리되는 숫자를 사람이 읽을 수 있는 문자로 바꿔주는 변한 열쇠 같은 것입니다. 이 열쇠가 없으면 온통 이상하게 뭉그러지고, 찌그러진 기호더미만 화면에 출력될 것입니다.

여러 종류의 텍스트를 한 군데 저장하고 처리해야 하는 상황도 한 번 생각 해봅시다. 예를 들어, 여러분이 받은 이메일을 한 데이터베이스에 저장하는 상황이 있을 수 있겠죠? 텍스트만 저장해두면 될까요? 아니죠. 텍스트를 제대로 화면에 표시하려면 사용한 문자 인코딩 정보도 함께 저장해야 합니다. 흠. 얼핏 봐도 쉽지 않을 거 같네요. 데이터베이스에 있는 이메일을 검색해야 하는 상황은 또 어떤가요? 검색 키워드를 각 이메일에 맞는 인코딩으로 그때 그때 변경해줘야 할 것 같군요. 별로 유쾌한 상황은 아니죠?

한 문서에 여러 언어의 문자를 섞어서 써야 하는 상황은 어떨까요? (힌트입니다: 실제로 워드프로세서는 한 문자 인코딩 모드에서 다른 문자 인코딩 모드로 변경할 때 마다 escape code를 사용합니다. 여러분 컴퓨터의 인코딩이 러시아 koi8-r 모드라면 숫자 241은 Я을 의미하지만, Mac Greek 모드로 바꾸는 순간 241은 ώ로 바뀝니다.) 당연히 이 문서도 특정 텍스트로 검색할 수 있어야 합니다.

이제까지 여러분이 알고 있던 문자열에 대한 지식은 모두 잘못된 것이라는 사실에 분노가 밀려올 수도 있겠습니다. 아무튼 컴퓨터 세상에서 "평범한 텍스트"라는 것은 존재하지 않습니다.

유니코드(Unicode)

유니코드로 들어갑시다.

유니코드(Unicode)란 지구상의 모든 언어를 표현하기 위해 고안된 시스템입니다. 유니코드는 모든 기호와 문자를 4바이트 숫자로 표현합니다. 각 숫자는 전 세계의 문자 각각에 고유하게 대응되어 겹치는 일이 없습니다. (4바이트로 된 모든 숫자를 다 쓰지는 않지만, 65535개 보다는 많이 쓰므로 2바이트로 다 표현할 수는 없습니다.) 여러 언어에서 공통적으로 쓰는 문자는 별다른 어원상의 문제가 없다면 일반적으로 같은 숫자를 가집니다. 어쨌거나 중요한 것은 문자 하나가 숫자 하나에 대응된다는 사실입니다. 모든 숫자는 오직 하나의 문자만을 의미하며, 기억해야 할 "모드" 같은 것은 없습니다. U+0041은 항상 'A'라는 문자이고, 여러분이 사용하는 언어에 'A'가 없더라도 마찬가지 입니다.

자, 이제 우리의 문제가 완벽하게 해결된 것 같지 않나요? 인코딩 하나가 모두를 지배합니다. 한 문서에 여러 언어를 사용할 수 있고, 인코딩을 바꾸기 위해 "모드 변경"을 하지 않아도 됩니다. 그런데 혹시 그런 생각 안드세요? "뭐? 4바이트나 사용한다고? 이거 너무 낭비가 심한 거 아니야?" 특히 문자 하나에 1바이트를 쓰던 영어권 사람이라면 더할 수도 있겠지만, 사실 표의 문자를 쓰는 중국에서도 한 문자당 2바이트 이상은 필요하지 않습니다.

아무튼 이렇게 한 문자를 4바이트로 표현하는 유니코드 인코딩 방식을 UTF-32라고 합니다. 32비트가 4바이트이기 때문에 그런 이름이 붙었습니다. UTF-32는 직관적입니다. 세계 어느 나라의 문자도 4바이트에 해당하는 숫자로 고유하게 표현할 수 있습니다. 덕분에 N번 째에 위치한 문자를 검색할 때 필요한 시간이 일정하다는 장점이 있습니다. 4*N바이트 위치를 검색하면 되니까요. 대신 각 문자마다 엄청난 양의 바이트를 할당해야 한다는 문제도 있지요.

유니코드 안에 어마어마한 양의 문자들이 있지만, 사람들은 곧 유니코드의 뒷 쪽에 배치되어 있는 특수기호나 문자는 거의 사용되지 않는다는 사실을 알게 됩니다. 그래서 UTF-16이라는 새로운 유니코드 인코딩이 등장하게 됩니다. (2바이트만 사용하기 때문이겠죠.) UTF-16은 2바이트(0-65535) 숫자에 대응되는 문자는 그대로 표현하고, 그 뒤의 "외계" 문자는 정말 필요할 경우 지저분한 꼼수를 사용해서 (어쨌든) 표현합니다. 장점은 명백합니다. UTF-16은 UTF-32에 비해 공간을 절반만 사용합니다. (2바이트를 넘는 숫자에 해당하는 문자는 빼고요.) 검색해야 하는 문자열에 "외계" 문자가 포함되어 있지만 않다면, N번 째 문자를 찾는 검색 속도도 일정합니다.

그러나 UTF-32에도 UTF-16에도 뭔가 뒷맛이 개운치 못한 문제가 있었으니, 그것은 바로 바이트를 저장하는 방식이 컴퓨터 시스템 마다 다르다는 것이었습니다. 예를 들어 UTF-16 문자 U+4E2D를 저장할 때, 시스템이 big-endian을 사용하면 4E 2D로, little-endian을 사용하면 2D 4E로 저장됩니다. (역자주: 인텔 CPU 기반의 윈도우와 리눅스 PC는 little-endian을 사용하고, RISC 기반의 UNIX 시스템이나 네트워크 시스템은 big-endian을 사용합니다.) UTF-32는 4바이트를 쓰기에 더 많은 정렬 방식이 존재합니다. 다른 사람과 문서를 주고 받을 일이 없다면 상관없겠지만, 다른 endian 방식을 사용하는 시스템에 문서를 보낼 경우에는 바이트 저장 순서가 어떻게 되는지도 알려주어야 합니다. 그렇지 않으면 받는 쪽에서 4E 2D라는 두 바이트 배열이 U+4E2D인지 U+2D4E인지 알 방법이 없습니다.

이런 문제를 해결하기 위해 멀티 바이트 유니코드 인코딩은 문서의 시작 부분에 "Byte Order Mark"라는 것을 정의합니다. 이것은 출력되지 않는 특수한 문자로, 바이트 저장 순서에 대한 정보를 담고 있습니다. UTF-16인 경우 Byte Order Mark는 U+FEFF입니다. 여러분이 받은 UTF-16 문서의 시작이 FF FE 인지, FE FF 인지에 따라 문서에서 사용된 바이트 순서가 결정됩니다.

UTF-16이 UTF-32에 비해 작은 용량을 차지하긴 하지만, ascii 문자를 많이 쓰는 경우라면 여전히 낭비가 심하다고 생각할 수 있습니다. 중국에 있는 웹페이지라 하더라도 대부분의 정보는 사실 ascii 문자로 표현되어 있습니다. 중국 문자를 감싸고 있는 HTML 요소(element)와 속성(attribute)을 생각해 보세요. N번 째에 위치한 문자를 검색할 때 필요한 시간이 일정하다는 건 좋지만, "외게" 문자를 사용할 경우에는 그마저도 보장할 수 없습니다. 별도의 색인(index)을 두지 않는 한 말이죠. 무엇보다 말이지요, 세상에는 ASCII 문자가 너무 많아요...

많은 사람들이 이 문제에 대해 생각했고, 해답을 찾아 내었습니다.


UTF-8

UTF-8은 길이가 변하는(variable-length) 유니코드 인코딩 시스템입니다. 즉, 문자 마다 차지하는 바이트 크기가 달라질 수 있다는 말이죠. ascii 문자(A-Z, 기타 기호)의 경우 한 문자가 한 바이트만 사용합니다. 사실 UTF-8의 첫 128개 문자(0-127)는 ASCII의 첫 128개 문자와 정확히 일치합니다. ñ 나 ö 같은 확장 라틴어는 2바이트를 사용합니다. (UTF-16 처럼 문자와 바이트가 바로 대응되는 것은 아니고, 비트를 이리 저리 좀 조작해야 하기는 합니다.) 中과 같은 중국어는 3바이트를 사용하고, 거의 사용되지 않는 "외계" 문자는 4바이트를 사용합니다.

물론 단점이 있습니다. 각 문자가 일정한 크기를 사용하지 않으므로, N번 째 문자를 찾는 속도가 일정하지 않고 O(N)에 가깝습니다. 즉, 총 문자열의 길이가 길어질수록 어떤 문자를 검색하는데 더 많은 시간이 소요됩니다. 또 문자를 바이트로 인코딩하거나, 바이트를 문자로 디코딩할 때 비트 연산을 사용해야 합니다.

장점은 어떨까요. ascii 문자를 인코딩할 때 탁월한 성능을 보여 줍니다. 확장 라틴어를 다룰 때는 UTF-16과 성능이 비슷하지요. 중국어의 경우 UTF-32보다 낫습니다. 그리고 비트 연산의 특성상, 바이트 순서와 관련된 문제가 없습니다. (일단 믿으세요. 곧 수학적으로 설명합니다.) UTF-8로 인코딩 된 문서는 어떤 컴퓨터를 쓰건 동일한 바이트 순서를 가집니다.

들어가 봅시다

파이썬 3에서 모든 문자열은 유니코드 문자의 나열입니다. UTF-8이나 CP-1251 등으로 인코딩 된 파이썬 문자열이라는 건 없습니다. "이거 UTF-8 문자열이에요?"는 잘못된 질문입니다. UTF-8은 문자열을 바이트 배열로 바꾸어 주는 방법 중의 하나입니다. 만약 어떤 문자열을 특정 인코딩 시스템을 사용하여 바이트 배열로 바꾸고 싶다면 파이썬 3를 이용할 수 있습니다. 반대로 어떤 바이트 배열을 문자열로 바꾸고 싶다면 역시 파이썬 3를 이용할 수 있습니다. 바이트는 문자가 아닙니다. 바이트는 그냥 바이트입니다. 문자는 추상화 된 것이고, 문자열은 그것들이 나열된 것입니다.

  1. 파이썬에서 문자열을 만드려면 따옴표로 묶어주면 됩니다. 파이썬 문자열은 홑따옴표(')나 겹따옴표(") 아무거나 사용하여 정의할 수 있습니다.
  2. 파이썬 내장함수인 len()은 문자열의 길이(문자의 수)를 반환합니다. 이 함수는 리스트, 튜플, 집합, 사전의 길이를 구하는 데에도 똑같이 사용할 수 있습니다. 문자열은 여러 문자의 튜플로 생각할 수도 있습니다.
  3. 마치 리스트에서 개별적인 아이템에 접근 하듯이, 인덱스(index) 표기법을 이용하면 개별 문자를 뽑아낼 수 있습니다.
  4. 리스트와 마찬가지로 + 연산자를 이용하여 한 문자열을 다른 문자열과 합칠 수도 있습니다.

문자열 서식(Formatting Strings)

humansize.py 코드를 다시 한 번 봅시다.

  1. 'KB', 'MB', 'GB' 등은 모두 각각 string 입니다.
  2. 함수의 docstring도 문자열입니다. 여러 줄에 걸쳐서 작성할 수 있고 시작과 끝을 나타내기 위해 따옴표 세 개를 연속으로 사용하면 됩니다.
  3. 이 따옴표 세 개는 docstring을 끝냅니다.
  4. 예외 메시지로 전달되는 문자열입니다.
  5. 아...이건.. 대체 뭘까요?

파이썬 3는 특정 값을 문자열 내부로 전달해서 출력할 수 있습니다. 이건 좀 복잡하니까 일단 값 하나를 문자열로 전달하는 방법을 알아봅시다.

  1. 실제로 제가 사용하는 패스워드는 아니예요.
  2. 여기서 많은 일이 벌어졌네요. 먼저 format()은 문자열의 메소드입니다. 문자열은 객체이고 객체는 메소드를 가질 수 있지요. 두 번째, 이 메소드 호출의 결과는 다시 문자열이 됩니다. 마지막으로, {0} 과 {1}은 치환 필드(replacement fields)라고 부르는 것인데, format()메소드의 인자로 전달 되는 값들이 그 위치에 들어갑니다.

복합 필드 이름(Compound Field Names)

바로 전에, 치환 필드가 정수 형태로 되어 있는 간단한 예제를 보았습니다. format() 메소드의 인자들이 순서대로 각 치환 필드에 들어갔지요. 첫 번째 인자(이 경우 username)는 {0}으로, 두 번째 인자(password)는 {1}로 들어갔습니다. 치환 필드와 인자의 수에 제한은 없습니다. 치환 필드는 지금 본 것보다 훨씬 많은 기능을 제공합니다.

  1. humansize 모듈의 함수를 호출하는 대신, 모듈 내부에 정의된 자료 구조인 SUFFIXES[1000]을 불러 "si"(1000의 지수승을 의미)_suffixes라는 리스트 하나를 정의했습니다.
  2. 얼핏 보기엔 복잡하지만, 그렇지 않습니다. {0}은 format() 메소드에 전달 된 첫 번째 인자인 si_suffixes를 의미합니다. 그런데 si_suffixes는 리스트입니다. 따라서 {0[0]}는 format() 메소드에 전달 된 첫 번째 인자인 si_suffixes의 첫 번째 아이템 'KB'가 되는 것이죠. 비슷하게, {0[1]}는 같은 리스트의 두 번째 아이템인 'MB'가 되고요. 중괄호 바깥에 있는 모든 것들은(1000, 등호, 빈 칸 등이요) 있는 그대로 출력됩니다. 그래서 최종적으로 출력되는 문자열은 '1000KB = 1MB'입니다.

이 예제에서 볼 수 있듯이, 치환 필드 내에 파이썬 문법을 써서 각종 데이터의 항목과 속성에 접근할 수 있습니다. 이를 복합 필드 이름(compound field name)이라고 합니다. 아래와 같은 것을 할 수 있습니다:

  • 리스트를 넘겨 주고, 리스트의 아이템을 인덱스를 통해 접근합니다 (앞의 예처럼요)
  • 사전을 넘겨 주고 사전의 키(key)를 이용하여 해당 값을 참조할 수 있습니다
  • 모듈을 넘겨 주고 모듈 내부의 변수와 함수를 참조할 수 있습니다
  • 클래스 인스턴스를 넘겨 주고, 그 인스턴스의 속성과 메소드를 참조할 수 있습니다
  • 앞에 열거 된 방법들을 섞어서 사용하는 것도 가능합니다

그럼 실제로 어떻게 사용할 수 있는지 예제를 통해 알아봅시다:

이제 코드에 대한 설명을 보시죠

  • sys 모듈은 현재 실행중인 파이썬 인스턴스에 대한 정보를 가지고 있습니다. sys 모듈 자체를 format() 메소드의 인자로 넘겨주었으므로, 치환 필드 {0}은 sys 모듈을 의미합니다.
  • sys.modules은 현재 실행중인 파이썬 인스턴스에서 import한 모든 모듈의 정보 사전 형태로 담고 있습니다. 키는 문자열로 된 모듈 이름이고, 값은 모듈 객체입니다. 그래서 치환 필드 {0.modules}는 import 된 모듈의 사전이 됩니다.
  • sys.modules['humansize']는 앞서 import한 humansize 모듈을 의미합니다. 따라서 치환 필드인 {0.modules[humansize]}도 humansize 모듈을 가르킵니다. 문법이 살짝 다른 부분이 있는데, 실제 파이썬 코드에서는 sys.modules 사전의 키로 문자열이 들어가기 때문에 모듈 이름을 'humansize'와 같이 따옴표로 감싸줘야 합니다. 그리나 치환 필드에서는 humansize로 따옴표를 사용하지 않았습니다. PEP 3101: Advanced String Formatting을 인용하자면, "아이템 키를 파싱하기 위한 규칙은 매우 간단합니다. 키가 숫자로 시작하면 숫자로 취급하고, 그 외의 경우에는 모두 문자열로 취급합니다."
  • sys.modules['humansize'].SUFFIXES는 humansize 모듈에 정의된 사전입니다. 치환 필드인 {0.modules[humansize].SUFFIXES}도 마찬가지입니다.
  • sys.modules['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][0]}는 이 두 개의 문자로 이루어진 문자열 KB가 됩니다.

Format Specifiers

잠깐만요! 아직 끝난 게 아니예요. humansize.py 코드에 있는 이상한 줄을 다시 한 번 보세요.

{1}은 format() 메소드의 두번째 인자인 suffix로 대체됩니다. 그런데 {0:.1f}은 뭐죠? 일단 두 부분으로 나눠서 생각 해봅시다. {0}은 첫 번째 인자를 의미하는 건 알겠지요? 그런데 :.1f는 아리송하군요. 이것은 형식 지정자(format specifier)라고 부르는 것인데, 치환된 변수가 출력될 때 보여지는 방식을 지정해주는 것입니다.

형식 지정자는 치환되는 텍스트를 다양한 방식으로 표현할 수 있게 해줍니다. C 언어의 printf() 함수와 같이 자릿수를 추가하거나, 글자 사이에 빈칸을 넣거나, 문자열을 정렬하거나, 심지어 10진수를 16진수로 변환할 수도 있습니다.

치환 필드 내에서 콜론(:)은 형식 지정자가 시작된다는 것을 의미합니다. 형식 지정자 ".1"은 "소숫점 둘째 자리에서 반올림 하라"는 의미입니다. (즉 소숫점 뒤의 한 자리만 나타내라는 것이죠). 형식 지정자 "f" 는 "fixed-point number(부동 소숫점 숫자)"라는 뜻이고요. 따라서, 이를 주어진 숫자 698.24와 접미어 'GB'에 적용하면, 형식 지정자는 '698.2 GB'이 됩니다.

형식 지정자에 대한 더 자세한 내용은 파이썬 공식 문서인 Format Specification Mini-Language를 참고하시기 바랍니다.

그 외 많이 쓰는 문자열 메소드

문자열에는 형식 지정 외에도 다른 유용한 기능이 많이 있습니다.

  1. 파이썬 대화형 쉘에서도 여러 줄의 문자열을 입력할 수 있습니다. 먼저 따옴표 세 개를 연달아 써서 문자열을 시작하고 ENTER 키를 누르면 다음 행으로 이동해 입력을 계속할 수 있습니다. 입력을 마치려면 시작할 때와 같이 따옴표 세 개를 연달아 써주면 됩니다. 다시 ENTER 키를 누르면 명령이 실행됩니다. (이 경우엔 해당 문자열이 변수 s에 할당되겠네요).
  2. splitlines() 메소드는 여러 줄 문자열 하나를 인자로 받은 뒤, 전체 문자열을 줄 별로 나눠 리스트로 반환합니다. 줄 끝에 입력된 carriage return(ENTER 키에 해당) 값은 포함되지 않습니다.
  3. lower() 메소드는 전체 문자열을 소문자로 변환합니다. (마찬가지로 upper() 메소드는 문자열을 대문자로 변환합니다.)
  4. count() 메소드는 인자로 전달된 문자열이 쓰인 수를 반환합니다. 네. 맞습니다. 문장 내에서 "f"는 정확히 6번 나옵니다!

다른 예도 봅시다. 마치 키-값의 쌍처럼 보이는 리스트(key1=value1&key2=value2와 같은) 포맷의 문자열이 있다고 생각해 봅시다. 이 문자열을 적절히 쪼개서 {key1: value1, key2: value2}의 사전으로 바꾸고 싶습니다.

  1. split() 메소드는 구분자(delimiter)를 인자로 받습니다. 제공된 구분자를 이용해 문자열을 쪼갠 뒤 리스트에 담아 반환합니다. 여기서 사용된 구분자는 ampersand(&) 문자이지만, 다른 어떤 문자도 사용할 수 있습니다.
  2. 이제 문자열('key=value' 형태)이 원소인 리스트를 갖게 되었습니다. 리스트 컴프리헨션을 이용하면 전체 리스트를 순회하면서 각 문자열을 등호를 기준으로 분리할 수 있습니다. split() 함수의 두 번째 인자는 구분자를 이용하여 몇 번을 나눌지 결정할 수 있습니다. 1은 한 번만 분할하라는 뜻이므로 = 표시를 기준으로 하여 키, 값 두 개의 항목을 분리해서 반환합니다. (이 예제에서는 안 써도 상관없지만, 등호가 여러 번 나오는 문자열의 경우는 주의를 기울여야 하겠지요. 예를 들어, 'key=value=foo'.split('=')는 ['key', 'value', 'foo']로 분리될 것입니다.)
  3. 마지막으로, dict() 함수를 이용하여 리스트의 리스트를 사전으로 변경합니다.

이번에 다뤘던 예제는 URL에 나오는 query 파라미터를 파싱하는 것과 유사하지만, 실제 URL을 파싱하는 일은 훨씬 더 복잡합니다. 만약 URL query 파라미터를 다뤄야 한다면 urllib.parse.parse_qs() 함수를 사용하는 편이 훨씬 낫습니다.

문자열 자르기(Slicing a String)

문자열의 일부를 취해서 새로운 문자열로 만드는 것도 가능합니다. 문자열을 자르는 것이지요. 문자열을 자르는 것은 리스트를 자르는 것과 거의 흡사합니다. 문자열이 문자의 나열이라는 것을 잊지 마세요.

  1. 문자열의 일부분(slice)을 얻으려면 두 개의 시작 인덱스와 종료 인덱스를 지정해주면 됩니다. 반환값은 문자열의 시작 인덱스부터 종료 인덱스 전까지의 문자열입니다.
  2. 리스트와 유사하게 음수를 인덱스로 주는 것도 가능합니다.
  3. 문자열의 시작 인덱스는 0입니다. 따라서 a_string[0:2]은 문자열의 처음부터 두 번째까지를 반환합니다. 시작되는 지점은 a_string[0]이고, 종료되는 지점은 a_string[2]의 바로 앞입니다.
  4. 만약 왼쪽 인덱스가 0이면 생략해도 됩니다. a_string[:18]은 a_string[0:18]과 같은 의미입니다.
  5. 마찬가지로, 우측의 인덱스가 문자열의 길이와 같다면 생략할 수 있습니다. a_string[18:]는 a_string[18:44]와 같은 의미입니다. a_string[:18]는 문자열의 첫앞 18 문자를 잘라 반환하고, a_string[18:] 은 그 뒷부분 전체를 반환합니다. 정리하면, a_string[:n]은 첫 n개의 글자를, a_string[n:]은 그 뒷 부분 전체를 반환합니다.

문자열 대 바이트

바이트는 바이트이고, 문자는 추상화된 것입니다. 변경 불가능한(immutable) 유니코드 문자의 나열이 문자열입니다. 바이트 객체가 문자열과 다른 점은, 유니코드 문자가 아닌 0부터 255 사이의 숫자로 되어 있다는 것입니다. 변경이 불가능하다는 점은 같습니다.

  1. 바이트 객체를 정의하기 위해서는 b'' 라는 형식의 문법을 사용합니다(byte literal이라고 합니다). byte literal 내에 있는 바이트는 ascii 문자이거나, 또는 인코딩 된 16진수(\x00 부터 \xff(0-255) 사이의 값을 가집니다)입니다.
  2. 바이트 객체의 타입은 바이트입니다.
  3. 바이트 객체의 길이를 구하기 위해서는 내장함수인 len() 함수를 사용하면 됩니다. 리스트나 문자열과 마찬가지죠.
  4. 바이트 객체를 합치기 위해서는 + 연산자를 사용하면 됩니다. 결과로 새로운 바이트 객체가 반환됩니다. 이 또한 문자열이나 리스트에서 사용하던 방식과 동일합니다.
  5. 5바이트 크기의 바이트 객체와 1바이트 크기의 바이트 객체를 합치면 6바이트 크기의 바이트 객체가 반환됩니다.
  6. 리스트나 문자열과 마찬가지로 바이트 객체 내의 개별 바이트를 얻기 위해 인덱스를 사용할 수 있습니다. 문자열에서 인덱스를 통해 문자열의 부분을 얻어내었듯이, 바이트 객체에서 인덱스를 통해 얻는 값은 0-255 사이의 정수입니다.
  7. 바이트 객체는 일단 생성되고 나면 변경이 불가능합니다. 이 객체 내의 개별 요소를 바꿀 수 없습니다. 바꾸고 싶다면 문자열을 자른 후에 다시 붙이던지(문자열과 같은 방식으로 작동해요), 아니면 바이트 객체를 바이트 배열 객체로 바꿉니다.

  1. 바이트 객체를 바이트 배열 객체로 변경하려면 내장함수인 bytearray()를 이용하면 됩니다.
  2. 바이트 객체에서 사용하는 메소드나 연산은 바이트 배열에도 그대로 사용이 가능합니다.
  3. 유일한 차이점은 바이트 배열에는 인덱스를 이용하여 개별 바이트 변경이 가능하다는 것입니다. 다만 대입되는 값은 0 부터 255 사이의 정수이어야 합니다.

바이트와 문자열을 섞는 일은 절대 하지 마세요.

  1. 바이트와 문자열은 합칠 수 없습니다. 서로 다른 타입임을 명심하세요.
  2. 문자열 안에서 바이트 객체의 수를 셀 수는 없습니다. 문자열 내에는 바이트가 없으니까요. 문자열은 문자의 나열입니다. 만약 "어떤 바이트를 특정 문자열 인코딩 방식으로 변환했을 때, 이에 해당하는 문자열을 찾고 싶었던" 거라면, 다른 명시적인 방법으로 표현 해줘야 합니다. 파이썬 3에서는 문자열과 바이트 사이의 묵시적인 형변환은 허용되지 않습니다.
  3. 이 줄에서는 "by라는 바이트 객체를 ascii 방식으로 디코딩한 뒤 반환되는 문자열이, s라는 문자열에 쓰인 개수를 찾으라"고 명시적으로 표현하였습니다.

바이트와 문자열은 상당히 밀접합니다. 바이트 객체는 decode()라는 메소드를 가지고 있는데, 사용할 인코딩 방식을 인자로 받아 해당 바이트를 문자열로 변환합니다. 반면, 문자열에는 encode()라는 메소드가 있고, 인코딩 방식을 인자로 받아 해당 문자열을 바이트로 변환합니다. 앞의 예제 3번을 보면 디코딩하는 과정이 쉽게 이해될 겁니다. ascii 방식으로 인코딩된 바이트를 문자열로 변환하고 있죠. 비슷한 방식을 통해, 지원되는 어떤 인코딩으로도 문자열과 바이트를 변환할 수 있습니다. 설령 유니코드 인코딩이 아니어도 됩니다.

  1. 9개의 문자로 된 문자열입니다.
  2. 바이트 객체네요. 길이는 13바이트입니다. a_string을 utf-8 형식으로 인코딩한 결과입니다.
  3. 바이트 객체이고, 길이는 11 바이트입니다. a_string 변수를 GB18030이라는 형식으로 인코딩한 결과입니다.
  4. 바이트 객체이고, 길이는 11 바이트 입니다. a_string 변수를 Big5라는 형식으로 인코딩한 결과입니다. 결과로 나오는 바이트가 인코딩에 따라 전혀 다른 것을 볼 수 있습니다.
  5. 9개의 문자로 구성된 문자열입니다. by라는 바이트 객체를 Big5 형식으로 디코딩한 결과입니다. 원래의 문자열과 같은 것을 볼 수 있습니다.

꼬릿말: 파이썬 소스 코드의 문자 인코딩

파이썬 3 는 여러분의 소스코드, 즉 .py 파일이 UTF-8으로 인코딩 되어 있다고 가정합니다.

파이썬 2 에서는 .py 파일에 대한 기본 인코딩이 ascii입니다. 파이썬 3에서는 UTF-8이 기본 인코딩입니다.

만약 UTF-8이 아닌 다른 인코딩 방식을 소스코드에 적용하고 싶다면, 소스코드 첫 줄에 인코딩 선언문을 명시하면 됩니다. 아래 선언문은 해당 파이썬 소스코드의 인코딩을 windows-1252로 지정합니다:

만약 첫 줄에 유닉스에서 쓰이는 hash-bang 커맨드를 입력해야 하는 경우에는, 두 번째 줄에 명시해도 됩니다.

더 자세한 내용은 PEP 263: Defining Python Source Code Encodings 문서를 참조하시기 바랍니다.

더 읽을거리

파이썬의 유니코드에 대해:

유니코드 전반에 대해 좀 더 알고 싶다면 아래 링크를 참조하세요:

다른 방식으로 문자열을 인코딩하는 방법을 알고 싶다면:

문자열에 대한 전반적인 내용과 형식 지정에 대해 궁금하다면:

이 강의는 영어로 된 원문한글화 프로젝트를 기초로 작성되었으며, Creative Commons Attribution Share-Alike 라이센스하에 자유로운 변경, 배포가 가능합니다

7145 읽음
이전 챕터 3. 컴프리헨션(Comprehensions)
다음 챕터 5. 정규표현식(Regular Expressions)

저자

토론이 없습니다

Please log in to leave a comment

16.5.11618.20190612.allo
문제 보고 · 사용조건 · 개인정보보호
래블업 주식회사 · 대한민국 서울 테헤란로 145 · 대표: 신정규 · 사업자번호: 864-88-00080 · +82 70-8200-2587

거절 확인

닫기
좋아요 책갈피 토론

아래 주소를 복사하세요