☜ 제 02 장 파이썬 첫 경험 | """ Dive Into Python """ 다이빙 파이썬 |
제 04 장 강력한 내부검사 ☞ |
딱 1분 후에 첫 파이썬 프로그램으로 되돌아 가 보겠습니다. 그러나 먼저 짧은 일탈이 준비되어 있습니다. 사전과 터플 그리고 리스트에 관하여 알아야할 필요가 있기 때문입니다. 여러분이 펄(Perl) 해커라면 아마도 사전과 리스트 정도는 건너뛰어도 되겠지만 터플에는 주의를 기울이셔야 할 것입니다.
파이썬의 내장 데이터유형중 하나는 사전입니다. 사전은 키와 값 사이에 일대일 관계를 정의합니다.
☞ | |
파이썬의 사전은 펄(Perl)의 해시와 비슷합니다. 펄(Perl)에서 해시가 저장된 변수는 언제나 % 문자로 시작합니다. 파이썬에서 변수는 마음대로 지어도 좋으며 파이썬이 내부적으로 그 데이터유형을 추적관리합니다. |
☞ | |
파이썬의 사전은 자바(Java)의 Hashtable 클래스의 실체와 비슷합니다. |
☞ | |
파이썬의 사전은 비주얼 베이직(Visual Basic)의 Scripting.Dictionary 객체의 실체와 비슷합니다. |
>>> d = {"server":"mpilgrim", "database":"master"} ① >>> d {'server': 'mpilgrim', 'database': 'master'} >>> d["server"] ② 'mpilgrim' >>> d["database"] ③ 'master' >>> d["mpilgrim"] ④ Traceback (innermost last): File "<interactive input>", line 1, in ? KeyError: mpilgrim
① | 먼저, 원소가 두 개인 사전을 새로 만들어 변수 d에 할당합니다. 각 원소는 키-값 쌍이며 원소 전체는 활괄호로 둘러싸입니다. |
② | 'server'는 키이고 d["server"]로 참조한 그의 연관 값은 'mpilgrim'입니다. |
③ | 'database'는 키이고 d["database"]로 참조한 그의 연관 값은 'master'입니다. |
④ | 키로 값을 얻을 수 있지만 값으로 키를 얻을 수는 없습니다. 그래서 d["server"]는 'mpilgrim'이지만 d["mpilgrim"]은 예외를 일으킵니다. 'mpilgrim'은 키가 아니기 때문입니다. |
>>> d {'server': 'mpilgrim', 'database': 'master'} >>> d["database"] = "pubs" ① >>> d {'server': 'mpilgrim', 'database': 'pubs'} >>> d["uid"] = "sa" ② >>> d {'server': 'mpilgrim', 'uid': 'sa', 'database': 'pubs'}
① | 사전에서 키는 중복될 수 없습니다. 기존의 키에 값을 할당하면 예전 값이 없어집니다. |
② | 언제든지 새로 키-값 쌍을 추가할 수 있습니다. 이 구문은 기존의 값을 변경할 때의 구문과 동일합니다. (그렇습니다. 언젠가 이 때문에 짜증날 때가 있을 것입니다. 새로운 값을 더하고 있다고 생각하지만 실제로는 같은 값을 바꾸고 또 바꾸게 됩니다. 왜냐하면 키가 생각대로 변하지 않기 때문입니다.) |
새로운 원소(키는 'uid'이고 값은 'sa'임)가 가운데에 나타난 것에 주목하세요. 사실, 첫 예제에서 순서대로 원소가 나타난 것은 그저 우연이었을 뿐입니다; 지금 순서대로 나타나지 않는 것 역시 그 만큼 우연입니다.
☞ | |
사전은 원소 사이에 순서 개념이 없습니다. 원소가 “순서가 없다”고 말하는 것은 올바르지 않습니다; 단순히 순서대로 널려 있지 않을 뿐입니다. 이것은 중요한 구별입니다. 이를 구별하지 못하면 사전의 원소에 특정하게 반복가능한 순서로 (예를 들어 키를 알파벳 순서로 하여) 접근하고 싶을 때 고민스러울 것입니다. 이렇게 하는 방법이 있지만 사전 안에 구축되어 들어가 있지 않습니다. |
사전으로 작업을 할 때 사전의 키가 대소문자를 구별한다는 것을 인식할 필요가 있습니다.
>>> d = {} >>> d["key"] = "value" >>> d["key"] = "other value" ① >>> d {'key': 'other value'} >>> d["Key"] = "third value" ② >>> d {'Key': 'third value', 'key': 'other value'}
>>> d {'server': 'mpilgrim', 'uid': 'sa', 'database': 'pubs'} >>> d["retrycount"] = 3 ① >>> d {'server': 'mpilgrim', 'uid': 'sa', 'database': 'master', 'retrycount': 3} >>> d[42] = "douglas" ② >>> d {'server': 'mpilgrim', 'uid': 'sa', 'database': 'master', 42: 'douglas', 'retrycount': 3}
>>> d {'server': 'mpilgrim', 'uid': 'sa', 'database': 'master', 42: 'douglas', 'retrycount': 3} >>> del d[42] ① >>> d {'server': 'mpilgrim', 'uid': 'sa', 'database': 'master', 'retrycount': 3} >>> d.clear() ② >>> d {}
리스트는 파이썬의 핵심 데이터유형입니다. (설마 그럴리는 없겠지만) 리스트에 대한 경험이 비주얼 베이직(Visual Basic)의 배열이나 파워빌더(Powerbuilder)의 데이터스토어일 뿐이라면 파이썬의 리스트에 단단히 대비하세요.
☞ | |
파이썬의 리스트는 펄(Perl)의 배열과 같습니다. 펄(Perl)에서 배열이 저장된 변수는 언제나 @ 문자로 시작합니다; 파이썬에서 변수는 아무 이름이나 상관없으며 내부적으로 파이썬이 그 데이터유형을 추적관리합니다. |
☞ | |
파이썬의 리스트는 자바(Java)의 배열 그 이상입니다 (물론, 삶에서 정말로 필요한 모든 것이라면 배열처럼 사용할 수 있습니다.). 좀 더 비유를 잘 들자면 ArrayList 클래스가 될 텐데 이 클래스는 어떤 객체도 담을 수 있으며 새로 원소가 추가될 때마다 확장될 수 있습니다. |
>>> li = ["a", "b", "mpilgrim", "z", "example"] ① >>> li ['a', 'b', 'mpilgrim', 'z', 'example'] >>> li[0] ② 'a' >>> li[4] ③ 'example'
>>> li ['a', 'b', 'mpilgrim', 'z', 'example'] >>> li[-1] ① 'example' >>> li[-3] ② 'mpilgrim'
>>> li ['a', 'b', 'mpilgrim', 'z', 'example'] >>> li[1:3] ① ['b', 'mpilgrim'] >>> li[1:-1] ② ['b', 'mpilgrim', 'z'] >>> li[0:3] ③ ['a', 'b', 'mpilgrim']
① | 지표를 두 개 지정하면 부분리스트를 얻을 수 있습니다. 이른바 “조각썰기(slice)”라고 부릅니다. 반환 값은 새로운 리스트로서 첫 조각 지표로부터 시작하여 (이 경우 li[1]), 두 번째 조각 지표 미만까지 (이 경우 li[3]) 리스트의 모든 원소를 순서대로 얻습니다. |
② | 조각썰기는 두 지표중 하나 또는 둘 모두 음수이더라도 작동합니다. 이런 식으로 생각하시면 좀 도움이 되겠습니다: 리스트를 왼쪽에서 오른쪽으로 읽어가면서 첫 조각 지표는 원하는 첫 요소를 지정하고 두번째 조각 지표는 원하지 않는 첫 요소를 지정한다고 생각하세요. 반환 값은 둘 사이에 있는 모든 것입니다. |
③ | 리스트는 지표가 0에서부터 시작합니다. 그래서 li[0:3]은 리스트에서 li[0]에서 시작하여 li[3] 미만까지 앞쪽 세 원소를 돌려줍니다. |
>>> li ['a', 'b', 'mpilgrim', 'z', 'example'] >>> li[:3] ① ['a', 'b', 'mpilgrim'] >>> li[3:] ② ③ ['z', 'example'] >>> li[:] ④ ['a', 'b', 'mpilgrim', 'z', 'example']
① | 왼쪽 조각 지표가 0이면 생략해도 좋습니다. 묵시적으로 0으로 간주됩니다. 그래서 예제 3.8, “리스트 조각썰기”에서 li[:3]은 li[0:3] 과 같습니다. |
② | 비슷하게, 오른쪽 조각 지표가 리스트의 길이이면 생략해도 됩니다. 그래서 li[3:]은 li[3:5]와 같은데 이 리스트는 원소가 다섯 개이기 때문입니다. |
③ | 대칭성에 주목하세요. 이 다섯 개 원소 리스트에서 li[:3]은 첫 세 원소를 돌려주고 li[3:]은 뒤의 두 원소를 돌려줍니다. 사실, li[:n]은 언제나 첫 n 원소를 돌려주며 li[n:]는 그 나머지를 돌려줍니다. 리스트이 길이에 상관없이 말입니다. |
④ | 두 조각 지표 모두 생략되면 리스트의 모든 원소가 포함됩니다. 그러나 이는 원래 li 리스트와 같지 않습니다; 새로운 리스트로서 우연히 원소가 모두 같을 뿐입니다. li[:]는 리스트를 완벽하게 복사하는 지름길입니다. |
>>> li ['a', 'b', 'mpilgrim', 'z', 'example'] >>> li.append("new") ① >>> li ['a', 'b', 'mpilgrim', 'z', 'example', 'new'] >>> li.insert(2, "new") ② >>> li ['a', 'b', 'new', 'mpilgrim', 'z', 'example', 'new'] >>> li.extend(["two", "elements"]) ③ >>> li ['a', 'b', 'new', 'mpilgrim', 'z', 'example', 'new', 'two', 'elements']
>>> li = ['a', 'b', 'c'] >>> li.extend(['d', 'e', 'f']) ① >>> li ['a', 'b', 'c', 'd', 'e', 'f'] >>> len(li) ② 6 >>> li[-1] 'f' >>> li = ['a', 'b', 'c'] >>> li.append(['d', 'e', 'f']) ③ >>> li ['a', 'b', 'c', ['d', 'e', 'f']] >>> len(li) ④ 4 >>> li[-1] ['d', 'e', 'f']
① | 리스트는 extend 메쏘드와 append 메쏘드 두 개가 있는데 마치 같은 일을 하는 것처럼 보입니다. 그러나 사실은 완전히 다릅니다. extend는 언제나 리스트인 인자를 하나 취해서 그 리스트의 원소를 낱낱이 원래 리스트에 추가합니다. |
② | 여기에서 원소 세 개짜리 리스트 ('a'와 'b' 그리고 'c')로 시작했습니다. 그리고 리스트를 또다른 원소 세 개짜리 리스트 ('d'와 'e' 그리고 'f')로 확장합니다. 그래서 원소 여섯 개짜리 리스트입니다. |
③ | 반면에 append는 데이터 유형에 상관없이 인자를 하나 취해서 리스트의 끝에 단순히 덧붙입니다. 여기에서 인자 하나로 append 메쏘드를 호출하고 있습니다. 이 인자는 원소가 세 개인 리스트입니다. |
④ | 이제 원소 세 개로 시작한 원래 리스트는 원소가 네 개입니다. 왜 네 개인가? 방금 마지막에 덧붙인 마지막 원소는 그 자체로 리스트입니다. 리스트는 다른 리스트를 포함하여 어떤 유형이든 담길 수 있습니다. 그것이 바로 원하는 것일 수도 있고 아닐 수도 있습니다. extend를 뜻할 경우는 append를 사용하지 마세요 . |
>>> li ['a', 'b', 'new', 'mpilgrim', 'z', 'example', 'new', 'two', 'elements'] >>> li.index("example") ① 5 >>> li.index("new") ② 2 >>> li.index("c") ③ Traceback (innermost last): File "<interactive input>", line 1, in ? ValueError: list.index(x): x not in list >>> "c" in li ④ False
① | index 메쏘드는 리스트에서 첫 번째 나타나는 값을 찾아 그 지표를 돌려줍니다. |
② | index 메쏘드는 리스트에서 처음 나타나는 값을 찾습니다. 이 경우, 'new'는 리스트에서 li[2]와 li[6]에서 두 번 나타납니다. 그러나 index는 오직 첫 지표만 돌려줍니다. 즉 2를 돌려줍니다. |
③ | 리스트에서 값이 발견되지 않으면 파이썬은 예외를 일으킵니다. 이는 대부분의 언어와 현저하게 다른데 다른 언어에서는 유효하지 않은 지표를 돌려줍니다. 이는 약간 성가셔 보일 수도 있습니다. 그러나 나중에 유효하지 않은 지표를 사용하려고 시도하는 것 보다 문제가 있는 소스에서 프로그램이 충돌을 일으킨다는 뜻이기 때문에 좋은 일입니다. |
④ | 리스트에 값이 있는지 테스트하려면 in을 사용하세요. 값이 발견되면 True를 돌려주고 그렇지 않으면 False를 돌려줍니다. |
☞ | |
2.2.1 이전 버전에서는 파이썬에 따로 불리언 데이터유형이 없었습니다. 이를 채우기 위해 파이썬은 (if 서술문 같은) 불리언 문맥에서 다음과 같은 규칙에 따라 거의 어떤 것이든 받아들였습니다:
|
>>> li ['a', 'b', 'new', 'mpilgrim', 'z', 'example', 'new', 'two', 'elements'] >>> li.remove("z") ① >>> li ['a', 'b', 'new', 'mpilgrim', 'example', 'new', 'two', 'elements'] >>> li.remove("new") ② >>> li ['a', 'b', 'mpilgrim', 'example', 'new', 'two', 'elements'] >>> li.remove("c") ③ Traceback (innermost last): File "<interactive input>", line 1, in ? ValueError: list.remove(x): x not in list >>> li.pop() ④ 'elements' >>> li ['a', 'b', 'mpilgrim', 'example', 'new', 'two']
① | remove는 리스트에서 제일 처음 출현하는 값을 제거합니다. |
② | remove는 제일 처음 나타나는 값만 삭제합니다. 이 경우, 'new'는 리스트에서 두 번 나타났지만 li.remove("new")는 첫 번째 나타난 것만 삭제합니다. |
③ | 리스트에서 값이 발견되지 않으면 파이썬은 예외를 일으킵니다. 이 행위는 index 메쏘드를 흉내낸 것입니다. |
④ | pop은 흥미로운 메쏘드입니다. 두 가지 일을 하는데: 리스트에서 가장 마지막 원소를 꺼내서 그 값을 돌려줍니다. 주의하세요. 값을 돌려주지만 리스트는 바뀌지 않는li[-1]와 다르며, 리스트를 바꾸지만 값은 돌려주지 않는 li.remove(value)와 다릅니다. |
>>> li = ['a', 'b', 'mpilgrim'] >>> li = li + ['example', 'new'] ① >>> li ['a', 'b', 'mpilgrim', 'example', 'new'] >>> li += ['two'] ② >>> li ['a', 'b', 'mpilgrim', 'example', 'new', 'two'] >>> li = [1, 2] * 3 ③ >>> li [1, 2, 1, 2, 1, 2]
① | 리스트는 + 연산자로 결합할 수 있습니다. list = list + otherlist는 결과가 list.extend(otherlist)와 같습니다. 그러나 + 연산자는 새로 (결합된) 리스트를 값으로 돌려주는 반면, extend는 기존의 리스트를 바꾸기만 할 뿐입니다. 이는 extend가 더 빠르며, 특히 커다란 리스트에 적합하다는 뜻입니다. |
② | 파이썬은 += 연산자를 지원합니다. li += ['two']는 li.extend(['two'])와 동등합니다. += 연산자는 문자열과 정수 그리고 리스트에 작동합니다. 그리고 사용자-정의 클래스에 맞게 작동하도록 오버로드도 가능합니다. (클래스에 관해서는 제 5 장에서 더 자세하게 다룹니다.) |
③ | * 연산자는 리스트에 반복자로 작동합니다. li = [1, 2] * 3는 li = [1, 2] + [1, 2] + [1, 2]와 동등한데, 세 리스트를 하나의 리스트로 결합한 것입니다. |
터플은 변경불능 리스트입니다. 터플은 일단 만들어지고 나면 어떤 방식으로도 변경이 불가능합니다.
>>> t = ("a", "b", "mpilgrim", "z", "example") ① >>> t ('a', 'b', 'mpilgrim', 'z', 'example') >>> t[0] ② 'a' >>> t[-1] ③ 'example' >>> t[1:3] ④ ('b', 'mpilgrim')
>>> t ('a', 'b', 'mpilgrim', 'z', 'example') >>> t.append("new") ① Traceback (innermost last): File "<interactive input>", line 1, in ? AttributeError: 'tuple' object has no attribute 'append' >>> t.remove("z") ② Traceback (innermost last): File "<interactive input>", line 1, in ? AttributeError: 'tuple' object has no attribute 'remove' >>> t.index("example") ③ Traceback (innermost last): File "<interactive input>", line 1, in ? AttributeError: 'tuple' object has no attribute 'index' >>> "z" in t ④ True
그래서 터플을 사용하기에 좋은 곳은 어디인가?
☞ | |
터플은 리스트로 변환할 수 있으며 그 반대도 가능합니다. 내장 tuple 함수는 리스트를 취해 같은 원소를 가진 터플을 돌려주고 list 함수는 터플을 취해 리스트를 돌려줍니다. 그 효과상 tuple은 리스트를 얼리고 list는 터플을 녹입니다. |
이제 사전과 터플 그리고 리스트에 관하여 좀 배웠습니다. 다시 제 2 장 odbchelper.py 샘플 프로그램으로 돌아 갑시다.
파이썬은 다른 언어와 마찬가지로 지역 변수와 전역 변수가 있습니다. 그러나 명시적으로 변수를 선언하지 않습니다. 변수는 값을 할당하면 튀어나와 존재하다가 영역을 벗어나면 자동으로 파괴되어 소멸합니다.
if __name__ == "__main__": myParams = {"server":"mpilgrim", \ "database":"master", \ "uid":"sa", \ "pwd":"secret" \ }
들여쓰기에 주목하세요. if 서술문은 코드 블록이며 함수처럼 들여쓰기 되어야 합니다.
또 주목할 것은 줄-계속 표식으로 기여하는 역사선 문자(“\”)로 여러 줄에 걸쳐서 한 명령어에 변수 할당이 이루어지고 있다는 것입니다.
☞ | |
한 명령어가 줄-계속 표식(“\”)으로 줄이 여러 개로 분리되면 계속되는 줄들도 같은 방식으로 들여쓰기 될 수 있습니다; 파이썬의 엄격한 들여쓰기 규칙은 적용되지 않습니다. 파이썬 IDE가 계속된 줄을 자동으로 들여쓰기 하면 아마도 그의 기본 값을 받아들여야 할 것입니다. 그렇게 하지 말아야 할 심각한 이유가 없는 한 말입니다. |
엄격히 말해 (반괄호)나 [각괄호] 또는 {활괄호} 안에 든 표현식은 (예를 들어 사전을 정의할 때) 줄 연속 문자(“\”)가 있든 없든 여러 줄로 분리가 가능합니다. 본인은 요구하지 않을 경우에도 역사전을 포함시키기를 좋아합니다. 왜냐하면 그래야 코드가 더 읽기 쉽다고 생각하기 때문입니다. 그러나 그것은 스타일의 문제입니다.
셋째, myParams 변수를 선언하지 않았습니다. 그냥 값을 거기에 할당했을 뿐입니다. 이는 VBScript에서 option explicit 옵션이 없는 것과 비슷합니다. 다행스럽게도 VBScript와는 다르게 파이썬은 값이 할당되지 않은 변수를 참조하는 것을 허용하지 않습니다; 그렇게 하면 예외가 일어납니다.
>>> x Traceback (innermost last): File "<interactive input>", line 1, in ? NameError: There is no variable named 'x' >>> x = 1 >>> x 1
파이썬이 이를 대신 처리해 주는 것에 대하여 언젠가는 감사할 날이 있을 겁니다.
파이썬의 멋진 지름길중 하나는 연속열을 사용하여 여러 값을 한 번에 할당하는 것입니다.
>>> v = ('a', 'b', 'e') >>> (x, y, z) = v ① >>> x 'a' >>> y 'b' >>> z 'e'
① | v는 원소가 세 개인 터플이며 (x, y, z)는 변수가 세 개인 터플입니다. 한 터플을 다른 터플에 할당하면 v의 값이 각 변수에 순서대로 할당됩니다. |
이 방법은 온갖 일에 사용됩니다. 종종 이름을 일정 범위의 값에 할당하고 싶은 경우가 있습니다. C라면 enum을 사용하여 손수 각 상수와 그의 연관 값을 나열하는데 이렇게 하면 값이 연속적일 경우 특히 지루해 보입니다. 파이썬에서는 여러-변수 할당과 함께 내장 range 함수를 사용하면 연속적 값을 즉시 할당할 수 있습니다.
>>> range(7) ① [0, 1, 2, 3, 4, 5, 6] >>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7) ② >>> MONDAY ③ 0 >>> TUESDAY 1 >>> SUNDAY 6
① | 내장 range 함수는 정수 리스트를 돌려줍니다. 가장 간단한 형태로 상한값을 취해 0-기반 리스트를 상한값 미만까지 세어 돌려줍니다. (다른 매개변수를 건네어 시작지표에 0말고 다른 값을 지정하고 뜀값에 1 말고 다른 값을 지정할 수 있습니다. 자세한 것은 range.__doc__을 인쇄해 보세요.) |
② | MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, 그리고 SUNDAY는 정의하려는 변수입니다. (이 예제는 calendar 모듈에서 빌려왔습니다. 이 모듈은 달력을 인쇄하는 재미있는 모듈입니다. UNIX에서 cal 프로그램과 비슷합니다. calendar 모듈은 요일에 대하여 정수 상수를 정의합니다.) |
③ | 이제 각 변수는 값이 있습니다: MONDAY는 0이고, TUESDAY는 1이며, 등등. |
다중-변수 할당을 사용하여 여러 값을 돌려주는 함수를 만들 수도 있습니다. 그냥 모든 값을 터플로 돌려주면 됩니다. 호출자는 그것을 터플로 취급할 수 있으며 그 값들을 변수에 따로따로 할당할 수 있습니다. 많은 표준 파이썬 라이브러리에서 이렇게 합니다. 여기에는 os 모듈이 포함되는데 이 모듈은 제 6 장에서 다루겠습니다.
파이썬은 값을 문자열로 형식화하는 방법을 지원합니다. 여기에는 아주 복잡한 표현식이 포함될 수 있지만 대부분의 기본적인 사용법은 %s 위치보유자로 값을 문자열에 삽입하는 것입니다.
☞ | |
파이썬의 문자열 형식화는 구문이 C의 sprintf 함수와 똑 같습니다. |
>>> k = "uid" >>> v = "sa" >>> "%s=%s" % (k, v) ① 'uid=sa'
① | 전체 표현식은 문자열로 평가됩니다. 첫 %s는 k의 값으로 교체되고; 두번째 %s는 v의 값으로 교체됩니다. 다른 모든 문자들은 (이 경우, 등호 사인) 그대로 있습니다. |
(k, v)는 터플임에 주의하세요. 터플은 쓸모가 있다고 말한 적이 있습니다.
단순한 문자열 결합에 이 정도는 너무 부담스럽다고 생각하실지 모르겠습니다. 문자열 형식화가 단순한 결합이 아니라는 점을 고려하지 않는다면 여러분의 생각이 옳을 수 있습니다. 단순히 형식화에 그치지 않고 유형도 강제로 변환합니다.
>>> uid = "sa" >>> pwd = "secret" >>> print pwd + " is not a good password for " + uid ① secret is not a good password for sa >>> print "%s is not a good password for %s" % (pwd, uid) ② secret is not a good password for sa >>> userCount = 6 >>> print "Users connected: %d" % (userCount, ) ③ ④ Users connected: 6 >>> print "Users connected: " + userCount ⑤ Traceback (innermost last): File "<interactive input>", line 1, in ? TypeError: cannot concatenate 'str' and 'int' objects
① | + 연산자는 문자열을 결합합니다. |
② | 이런 사소한 예에서는 문자열 형식화로 문자열 결합과 같은 결과를 달성합니다. |
③ | (userCount, )는 원소가 하나인 터플입니다. 맞습니다. 구문은 좀 이상하지만 그럴 만한 이유가 있습니다: 확실하게 터플임을 명시합니다. 사실, 리스트나 터플 또는 사전을 정의할 때 언제나 마지막 원소 다음에 쉼표를 포함시켜도 됩니다. 그러나 쉼표는 원소 한개짜리 터플을 정의할 때 필수 입니다. 쉼표가 없으면 파이썬은 (userCount)가 원소 한개짜리 터플인지 아니면 그냥 userCount의 값인지 알지 못합니다. |
④ | 문자열 형식화는 %s 대신에 %d를 지정하면 정수와 작동합니다. |
⑤ | 문자열을 문자열이 아닌 것과 결합하려고 하면 예외가 일어납니다. 문자열 형식화와 다르게 문자열 결합은 모든 것이 문자열일 경우에만 작동합니다. |
C의 printf에서처럼 파이썬의 문자열 형식화는 스위스 군용 칼과 같습니다. 수 많은 다양한 유형의 값을 특별하게 형식화하기 위한 옵션이, 즉 장식 문자열이 풍부합니다.
>>> print "Today's stock price: %f" % 50.4625 ① 50.462500 >>> print "Today's stock price: %.2f" % 50.4625 ② 50.46 >>> print "Change since yesterday: %+.2f" % 1.5 ③ +1.50
파이썬의 강력한 특징중 하나는 지능형 리스트인데 이는 한 리스트를 또다른 리스트에 간결하게 짝짓는 방법을 제공합니다. 리스트의 각 원소에 함수를 짝지으면 됩니다.
>>> li = [1, 9, 8, 4] >>> [elem*2 for elem in li] ① [2, 18, 16, 8] >>> li ② [1, 9, 8, 4] >>> li = [elem*2 for elem in li] ③ >>> li [2, 18, 16, 8]
다음은 제 2 장에 선언된 buildConnectionString 함수 안에 있는 지능형 리스트입니다:
["%s=%s" % (k, v) for k, v in params.items()]
먼저, params 사전의 items 함수를 호출하고 있음을 주목하세요. 이 함수는 사전의 모든 데이터를 터플에 넣어 리스트로 돌려줍니다.
>>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"} >>> params.keys() ① ['server', 'uid', 'database', 'pwd'] >>> params.values() ② ['mpilgrim', 'sa', 'master', 'secret'] >>> params.items() ③ [('server', 'mpilgrim'), ('uid', 'sa'), ('database', 'master'), ('pwd', 'secret')]
① | 사전의 keys 메쏘드는 모든 키를 리스트에 담아 돌려줍니다. 리스트의 순서가 사전에 정의된 순서와 같지 않습니다 (사전의 원소는 순서가 없다는 것을 기억하세요). |
② | values 메쏘드는 모든 값을 리스트에 담아 돌려줍니다. 이 리스트는 keys가 돌려준 리스트와 순서가 같습니다. 그래서 n의 모든 값에 대하여 params.values()[n] == params[params.keys()[n]]입니다. |
③ | items 메쏘드는 (key, value) 형태의 터플을 리스트에 담아 돌려줍니다. 리스트에 사전의 모든 데이터가 담깁니다. |
이제 buildConnectionString가 무슨 일을 하는지 살펴 보겠습니다. params.items()라는 리스트를 취해 각 원소에 문자열 형식화를 적용하여 새로운 리스트에 짝짓습니다. 새로운 리스트는 원소의 개수가 params.items()와 같지만 새 리스트의 각 원소는 params 사전의 키와 그의 연관 값을 담은 문자열이 됩니다.
>>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"} >>> params.items() [('server', 'mpilgrim'), ('uid', 'sa'), ('database', 'master'), ('pwd', 'secret')] >>> [k for k, v in params.items()] ① ['server', 'uid', 'database', 'pwd'] >>> [v for k, v in params.items()] ② ['mpilgrim', 'sa', 'master', 'secret'] >>> ["%s=%s" % (k, v) for k, v in params.items()] ③ ['server=mpilgrim', 'uid=sa', 'database=master', 'pwd=secret']
① | 두 변수를 사용하여 params.items() 리스트를 회돌이하고 있음을 주목하세요. 이는 여러-변수 할당의 또다른 사용법입니다. params.items()의 첫 원소는 ('server', 'mpilgrim')입니다. 그래서 지능형 리스트를 처음 반복할 때 k는 'server'를 얻고 v는 'mpilgrim'을 얻습니다. 이 경우, 반환된 리스트에서 v의 값은 무시하고 오직 k의 값만 포함시키고 있습니다. 그래서 이 지능형 리스트는 결국 params.keys()와 동등하게 됩니다. |
② | 같은 일을 하고 있지만 k의 값을 무시합니다. 그래서 이 지능형 리스트는 결국 params.values()와 동등하게 됩니다. |
③ | 앞의 두 예제와 간단한 문자열 형식화를 조합하면 사전의 각 원소의 키와 값을 포함하는 문자열을 리스트로 얻을 수 있습니다. 이제 거의 우리 프로그램의 출력처럼 보입니다. 이 리스트의 원소들을 하나의 문자열로 결합하는 일만 남았습니다. |
키-값 쌍을 key=value의 형태로 가지고 있고 그것을 하나의 문자열로 결합하고 싶습니다. 문자열 리스트를 하나의 문자열로 결합하려면 문자열 객체의 join 메쏘드를 사용합니다.
다음은 buildConnectionString 함수에서 리스트를 결합하는 예입니다:
return ";".join(["%s=%s" % (k, v) for k, v in params.items()])
계속 읽기 전에 흥미로운 주의사항 하나가 있습니다. 함수도 객체이고, 문자열도 객체이며, ... 모든 것이 다 객체라고 반복해서 말했습니다. 어쩌면 문자열 변수도 객체라고 생각하실지 모르겠습니다. 그러나 아닙니다. 이 예제를 자세히 살펴보시면 문자열 ";"가 바로 객체이며 그의 join 메쏘드를 호출하고 있는 것을 알 수 있습니다.
join 메쏘드는 리스트의 원소들을 하나의 문자열로 결합합니다. 각 원소는 쌍반점으로 갈라서 말이지요. 구분자가 반드시 쌍반점일 필요는 없습니다; 심지어 한개짜리 문자일 필요조차 없습니다. 문자열이면 무엇이든 됩니다.
◈ | |
join 메쏘드는 문자열 리스트에만 작동합니다; 강제로 유형을 변환하지 않습니다. 문자열이 아닌 원소를 하나 이상 리스트로 결합하면 예외가 일어납니다. |
>>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"} >>> ["%s=%s" % (k, v) for k, v in params.items()] ['server=mpilgrim', 'uid=sa', 'database=master', 'pwd=secret'] >>> ";".join(["%s=%s" % (k, v) for k, v in params.items()]) 'server=mpilgrim;uid=sa;database=master;pwd=secret'
그러면 이 문자열은 odbchelper 함수로부터 반환되고 호출 블록에 의하여 인쇄됩니다. 그리하여 이 장을 처음 읽을 때 경탄해 마지 않던 그 출력을 보게됩니다.
문자열을 리스트로 가르는 유사 메쏘드가 있지 않을까 의문이 드실겁니다. 물론 있습니다. 이른바 split 메쏘드가 바로 그것입니다.
>>> li = ['server=mpilgrim', 'uid=sa', 'database=master', 'pwd=secret'] >>> s = ";".join(li) >>> s 'server=mpilgrim;uid=sa;database=master;pwd=secret' >>> s.split(";") ① ['server=mpilgrim', 'uid=sa', 'database=master', 'pwd=secret'] >>> s.split(";", 1) ② ['server=mpilgrim', 'uid=sa;database=master;pwd=secret']
√ | |
anystring.split(delimiter, 1)는 문자열에서 부분문자열을 검색한 다음 그 부분문자열 앞에 있는 것(첫번째 원소)을 그리고 그 다음에 있는 것(두 번째 원소)을 처리할 때 유용한 테크닉입니다 . |
본인은 처음 파이썬을 배울 때 join이 리스트의 메쏘드가 되어 구분자를 인자로 받아야 한다고 기대했었습니다. 많은 사람들이 같은 생각이었는데 join 메쏘드 뒤에는 숨겨진 뒷이야기가 있습니다. 파이썬 1.6 이전에서 문자열은 이런 유용한 메쏘드들을 모두 갖추고 있지 못했습니다. 따로 string 모듈이 있었는데 여기에 모든 문자열 함수가 담겨 있었습니다; 함수마다 모두 문자열을 그의 첫 인자로 받았습니다. 함수들은 문자열 자체보다 우위에 둘 만큼 중요하게 여겨졌습니다. lower와 upper 그리고 split과 같은 함수는 그 만한 의미가 있었습니다. 많은 파이썬 프로그래머가 이 새로운 join 메쏘드를 반대합니다. 대신에 리스트 메쏘드가 되거나 또는 아무데도 옮기지 말고 오히려 예전 string 모듈의 일부가 되는 편이 더 좋다고 주장합니다 (여전히 그 안에 유용한 것들이 수 없이 많습니다). 여기에서는 단독으로 새로운 join 메쏘드를 사용했지만 어느 방식으로 코드를 작성하셔도 상관없습니다. 정말 신경이 쓰인다면 대신에 예전의 string.join 함수를 사용하시면 됩니다.
odbchelper.py 프로그램과 그의 출력은 이제 완전히 이해가 되셨을 줄로 믿습니다.
def buildConnectionString(params): """Build a connection string from a dictionary of parameters. Returns string.""" return ";".join(["%s=%s" % (k, v) for k, v in params.items()]) if __name__ == "__main__": myParams = {"server":"mpilgrim", \ "database":"master", \ "uid":"sa", \ "pwd":"secret" \ } print buildConnectionString(myParams)
다음은 odbchelper.py의 출력입니다:
server=mpilgrim;uid=sa;database=master;pwd=secret
다음 장으로 들어가기 전에 이 모든 것들을 몸에 익히시기를 바랍니다:
☜ 제 02 장 파이썬 첫 경험 | """ Dive Into Python """ 다이빙 파이썬 |
제 04 장 강력한 내부검사 ☞ |