☜ 제 02 장 파이썬 첫 경험 """ Dive Into Python """
다이빙 파이썬
제 04 장 강력한 내부검사 ☞

제 3 장 고유의 데이터유형

딱 1분 후에 첫 파이썬 프로그램으로 되돌아 가 보겠습니다. 그러나 먼저 짧은 일탈이 준비되어 있습니다. 사전과 터플 그리고 리스트에 관하여 알아야할 필요가 있기 때문입니다. 여러분이 펄(Perl) 해커라면 아마도 사전과 리스트 정도는 건너뛰어도 되겠지만 터플에는 주의를 기울이셔야 할 것입니다.

3.1. 사전 소개

파이썬의 내장 데이터유형중 하나는 사전입니다. 사전은 키와 값 사이에 일대일 관계를 정의합니다.

파이썬의 사전은 펄(Perl)의 해시와 비슷합니다. 펄(Perl)에서 해시가 저장된 변수는 언제나 % 문자로 시작합니다. 파이썬에서 변수는 마음대로 지어도 좋으며 파이썬이 내부적으로 그 데이터유형을 추적관리합니다.
파이썬의 사전은 자바(Java)의 Hashtable 클래스의 실체와 비슷합니다.
파이썬의 사전은 비주얼 베이직(Visual Basic)의 Scripting.Dictionary 객체의 실체와 비슷합니다.

3.1.1. 사전 정의

예제 3.1. 사전 정의하기

>>> 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'은 키가 아니기 때문입니다.

3.1.2. 사전 변경하기

예제 3.2. 사전 변경하기

>>> 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'임)가 가운데에 나타난 것에 주목하세요. 사실, 첫 예제에서 순서대로 원소가 나타난 것은 그저 우연이었을 뿐입니다; 지금 순서대로 나타나지 않는 것 역시 그 만큼 우연입니다.

사전은 원소 사이에 순서 개념이 없습니다. 원소가 “순서가 없다”고 말하는 것은 올바르지 않습니다; 단순히 순서대로 널려 있지 않을 뿐입니다. 이것은 중요한 구별입니다. 이를 구별하지 못하면 사전의 원소에 특정하게 반복가능한 순서로 (예를 들어 키를 알파벳 순서로 하여) 접근하고 싶을 때 고민스러울 것입니다. 이렇게 하는 방법이 있지만 사전 안에 구축되어 들어가 있지 않습니다.

사전으로 작업을 할 때 사전의 키가 대소문자를 구별한다는 것을 인식할 필요가 있습니다.

예제 3.3. 사전의 키는 대소문자에 민감하다

>>> d = {}
>>> d["key"] = "value"
>>> d["key"] = "other value" 
>>> d
{'key': 'other value'}
>>> d["Key"] = "third value" 
>>> d
{'Key': 'third value', 'key': 'other value'}
사전에서 기존의 키에 값을 할당하면 그냥 예전 값을 새로운 값으로 교체합니다.
이는 기존의 사전 키에 값을 할당하는 것이 아닙니다. 왜냐하면 파이썬에서 문자열은 대소문자가 구별되므로 'key''Key'는 서로 같지 않기 때문입니다. 이 때문에 새로 키/값 쌍이 사전에 생성됩니다; 여러분의 눈에는 비슷해 보이지만 파이썬의 관점에서는 완전히 다릅니다.

예제 3.4. 사전에서 데이터유형 섞어 쓰기

>>> 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}
사전은 문자열에만 적용되는 것이 아닙니다. 사전의 값은 어떤 데이터유형도 될 수 있습니다. 문자열이나 정수 또는 객체 심지어 다른 사전도 가능합니다. 사전 안에서 값들은 유형이 모두 같을 필요가 없습니다; 얼마든지 섞어 쓸 수 있습니다.
사전의 키는 조금 제한이 있지만 문자열과 정수 그리고 기타 다른 유형이 될 수 있습니다. 역시 사전 안에서 키 데이터유형을 섞어 쓸 수 있습니다.

3.1.3. 사전으로부터 원소 제거

예제 3.5. 사전으로부터 원소 제거하기

>>> 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
{}
del을 사용하면 키로 사전에서 원소를 지울 수 있습니다.
clear는 사전에서 모든 원소를 삭제합니다. 비어 있는 활괄호 세트는 원소가 하나도 없는 사전을 뜻합니다.

사전에 관하여 더 읽어야 할 것

3.2. 리스트 소개

리스트는 파이썬의 핵심 데이터유형입니다. (설마 그럴리는 없겠지만) 리스트에 대한 경험이 비주얼 베이직(Visual Basic)의 배열이나 파워빌더(Powerbuilder)의 데이터스토어일 뿐이라면 파이썬의 리스트에 단단히 대비하세요.

파이썬의 리스트는 펄(Perl)의 배열과 같습니다. 펄(Perl)에서 배열이 저장된 변수는 언제나 @ 문자로 시작합니다; 파이썬에서 변수는 아무 이름이나 상관없으며 내부적으로 파이썬이 그 데이터유형을 추적관리합니다.
파이썬의 리스트는 자바(Java)의 배열 그 이상입니다 (물론, 삶에서 정말로 필요한 모든 것이라면 배열처럼 사용할 수 있습니다.). 좀 더 비유를 잘 들자면 ArrayList 클래스가 될 텐데 이 클래스는 어떤 객체도 담을 수 있으며 새로 원소가 추가될 때마다 확장될 수 있습니다.

3.2.1. 리스트 정의하기

예제 3.6. 리스트 정의하기

>>> li = ["a", "b", "mpilgrim", "z", "example"] 
>>> li
['a', 'b', 'mpilgrim', 'z', 'example']
>>> li[0]                                       
'a'
>>> li[4]                                       
'example'
먼저, 원소가 다섯 개인 리스트를 정의합니다. 원래의 순서를 유지하고 있음에 주목하세요. 이는 우연이 아닙니다. 리스트는 순서가 있는 집합으로서 각괄호로 둘러싸입니다.
리스트는 0-기반 배열처럼 사용할 수 있습니다. 리스트의 첫 요소는 언제나 li[0]입니다.
이 원소 다섯 개짜리 리스트에서 마지막 원소는 li[4]인데 왜냐하면 리스트는 언제나 지표가 0에서부터 시작하기 때문입니다.

예제 3.7. 리스트의 음수 지표

>>> li
['a', 'b', 'mpilgrim', 'z', 'example']
>>> li[-1] 
'example'
>>> li[-3] 
'mpilgrim'
음의 지표는 리스트의 끝에서부터 거꾸로 세어 원소에 접근합니다. 리스트의 마지막 요소는 언제나 li[-1]입니다.
음의 지표를 잘 모르겠다면 이런 식으로 생각해 보세요: li[-n] == li[len(li) - n]. 그래서 이 리스트에서는 li[-3] == li[5 - 3] == li[2]입니다.

예제 3.8. 리스트 조각썰기

>>> 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] 미만까지 앞쪽 세 원소를 돌려줍니다.

예제 3.9. 간편 조각썰기

>>> 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[:]는 리스트를 완벽하게 복사하는 지름길입니다.

3.2.2. 리스트에 원소 추가하기

예제 3.10. 리스트에 원소 추가하기

>>> 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']
append는 원소 하나를 리스트의 끝에 추가합니다.
insert는 원소 하나를 리스트에 끼워 넣습니다. 숫자 인자는 위치가 바뀌어 밀려나는 첫 원소의 지표입니다. 리스트 원소는 유일할 필요가 없다는 것을 주목하세요; 현재 똑 같은 값 'new'li[2]li[6]에 따로 원소가 있습니다.
extend는 리스트를 결합합니다. 여러 인자로 extend를 호출하고 있지 않다는 것을 주목하세요; 인자는 단 하나, 즉 리스트로 호출합니다. 이 경우 리스트는 원소가 두 개입니다.

예제 3.11. extendappend 사이의 차이점

>>> 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를 사용하지 마세요 .

3.2.3. 리스트 검색하기

예제 3.12. 리스트 검색하기

>>> 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 서술문 같은) 불리언 문맥에서 다음과 같은 규칙에 따라 거의 어떤 것이든 받아들였습니다:
  • 0은 거짓이다; 다른 숫자는 모두 참이다.
  • 빈 문자열("")은 거짓이다; 다른 모든 문자열은 참이다.
  • 빈 리스트([])는 거짓이다; 다른 모든 리스트는 참이다.
  • 빈 터플(())은 거짓이다; 다른 모든 터플은 참이다.
  • 빈 사전({})은 거짓이다; 다른 모든 사전은 참이다.
이 규칙은 여전히 파이썬 2.2.1 이후에도 적용되지만 이제는 실제로 TrueFalse를 가지는 불리언 값을 사용할 수도 있습니다. 대문자에 주의하세요; 이 값들은 파이썬의 다른 것들과 마찬가지로 대소문자에 민감합니다.

3.2.4. 리스트에서 원소 제거

예제 3.13. 리스트로부터 원소 제거하기

>>> 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)와 다릅니다.

3.2.5. 리스트 연산자 사용하기

예제 3.14. 리스트 연산자

>>> 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] * 3li = [1, 2] + [1, 2] + [1, 2]와 동등한데, 세 리스트를 하나의 리스트로 결합한 것입니다.

리스트에 관하여 더 읽어야 할 것

3.3. 터플 소개

터플은 변경불능 리스트입니다. 터플은 일단 만들어지고 나면 어떤 방식으로도 변경이 불가능합니다.

예제 3.15. 터플 정의하기

>>> t = ("a", "b", "mpilgrim", "z", "example") 
>>> t
('a', 'b', 'mpilgrim', 'z', 'example')
>>> t[0]                                       
'a'
>>> t[-1]                                      
'example'
>>> t[1:3]                                     
('b', 'mpilgrim')
터플은 리스트와 같은 방식으로 정의됩니다. 단, 전체 원소가 각괄호가 아니라 반괄호에 둘러 싸이는 점이 다릅니다.
터플의 원소는 리스트처럼 순서가 정의됩니다. 터플 지표는 리스트처럼 0부터 시작합니다. 그래서 터플의 첫 원소는 언제나 t[0]입니다.
음의 지표는 리스트처럼 터플의 끝에서부터 셉니다.
리스트처럼 조각썰기도 가능합니다. 리스트를 조각썰면 새로운 리스트를 얻습니다; 터플을 조각썰면 새로운 터플을 얻습니다.

예제 3.16. 터플에는 메쏘드가 없다

>>> 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
터플에 원소를 추가할 수 없습니다. 터플은 appendextend 메쏘드가 없습니다.
터플로부터 원소를 제거할 수 없습니다. 터플은 removepop 메쏘드가 없습니다.
터플에서 원소를 찾을 수 없습니다. 터플은 index 메쏘드가 없습니다.
그렇지만, in을 사용하면 원소가 터플 안에 존재하는지 알아볼 수 있습니다.

그래서 터플을 사용하기에 좋은 곳은 어디인가?

  • 터플은 리스트보다 빠릅니다. 항상 값을 상수로 정의하고서 그 값을 반복해 사용할 생각이라면 리스트 대신 터플을 사용하세요.
  • 바꿀 필요가 없는 “쓰기-불가” 데이터라면 그렇게 하는 편이 코드가 더 안전합니다. 리스트 대신 터플을 사용하면 묵시적으로 assert 서술문을 사용하는 셈입니다. 이 데이터는 상수이고 오버라이드하려면 심사숙고(그리고 특정 함수)가 요구된다는 것을 보여줍니다.
  • 사전의 키는 정수와 문자열 그리고 “기타 몇 가지 유형”이 될 수 있다고 말한 것이 기억나십니까? 터플이 물론 그런 유형입니다. 터플은 사전의 키로 사용할 수 있지만 리스트는 이런 식으로 사용할 수 없습니다. 실제로 그 보다 더 복잡합니다. 사전의 키는 변경불능 유형이라야 합니다. 터플 그 자체는 변경불능입니다. 그러나 터플 안에 리스트를 담고 있다면 그것은 변경 가능으로 간주되므로 사전의 키로 사용하면 안전하지 않습니다. 오직 터플 안에 문자열이나 숫자 또는 기타 사전에-안전한 터플이 들어 있어야 사전의 키로 안전하게 사용할 수 있습니다.
  • 터플은 문자열 형식화와 사용됩니다. 잠시 후에 살펴보겠습니다.
터플은 리스트로 변환할 수 있으며 그 반대도 가능합니다. 내장 tuple 함수는 리스트를 취해 같은 원소를 가진 터플을 돌려주고 list 함수는 터플을 취해 리스트를 돌려줍니다. 그 효과상 tuple은 리스트를 얼리고 list는 터플을 녹입니다.

터플에 관하여 더 읽어야 할 것

3.4. 변수 선언

이제 사전과 터플 그리고 리스트에 관하여 좀 배웠습니다. 다시 제 2 장 odbchelper.py 샘플 프로그램으로 돌아 갑시다.

파이썬은 다른 언어와 마찬가지로 지역 변수와 전역 변수가 있습니다. 그러나 명시적으로 변수를 선언하지 않습니다. 변수는 값을 할당하면 튀어나와 존재하다가 영역을 벗어나면 자동으로 파괴되어 소멸합니다.

예제 3.17. myParams 변수 정의하기

if __name__ == "__main__":
    myParams = {"server":"mpilgrim", \
                "database":"master", \
                "uid":"sa", \
                "pwd":"secret" \
                }

들여쓰기에 주목하세요. if 서술문은 코드 블록이며 함수처럼 들여쓰기 되어야 합니다.

또 주목할 것은 줄-계속 표식으로 기여하는 역사선 문자(“\”)로 여러 줄에 걸쳐서 한 명령어에 변수 할당이 이루어지고 있다는 것입니다.

한 명령어가 줄-계속 표식(“\”)으로 줄이 여러 개로 분리되면 계속되는 줄들도 같은 방식으로 들여쓰기 될 수 있습니다; 파이썬의 엄격한 들여쓰기 규칙은 적용되지 않습니다. 파이썬 IDE가 계속된 줄을 자동으로 들여쓰기 하면 아마도 그의 기본 값을 받아들여야 할 것입니다. 그렇게 하지 말아야 할 심각한 이유가 없는 한 말입니다.

엄격히 말해 (반괄호)나 [각괄호] 또는 {활괄호} 안에 든 표현식은 (예를 들어 사전을 정의할 때) 줄 연속 문자(“\”)가 있든 없든 여러 줄로 분리가 가능합니다. 본인은 요구하지 않을 경우에도 역사전을 포함시키기를 좋아합니다. 왜냐하면 그래야 코드가 더 읽기 쉽다고 생각하기 때문입니다. 그러나 그것은 스타일의 문제입니다.

셋째, myParams 변수를 선언하지 않았습니다. 그냥 값을 거기에 할당했을 뿐입니다. 이는 VBScript에서 option explicit 옵션이 없는 것과 비슷합니다. 다행스럽게도 VBScript와는 다르게 파이썬은 값이 할당되지 않은 변수를 참조하는 것을 허용하지 않습니다; 그렇게 하면 예외가 일어납니다.

3.4.1. 변수 참조

예제 3.18. 묶이지 않은 변수 참조하기

>>> x
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
NameError: There is no variable named 'x'
>>> x = 1
>>> x
1

파이썬이 이를 대신 처리해 주는 것에 대하여 언젠가는 감사할 날이 있을 겁니다.

3.4.2. 여러 값을 한 번에 할당하기

파이썬의 멋진 지름길중 하나는 연속열을 사용하여 여러 값을 한 번에 할당하는 것입니다.

예제 3.19. 여러 값을 한 번에 할당하기

>>> v = ('a', 'b', 'e')
>>> (x, y, z) = v     
>>> x
'a'
>>> y
'b'
>>> z
'e'
v는 원소가 세 개인 터플이며 (x, y, z)는 변수가 세 개인 터플입니다. 한 터플을 다른 터플에 할당하면 v의 값이 각 변수에 순서대로 할당됩니다.

이 방법은 온갖 일에 사용됩니다. 종종 이름을 일정 범위의 값에 할당하고 싶은 경우가 있습니다. C라면 enum을 사용하여 손수 각 상수와 그의 연관 값을 나열하는데 이렇게 하면 값이 연속적일 경우 특히 지루해 보입니다. 파이썬에서는 여러-변수 할당과 함께 내장 range 함수를 사용하면 연속적 값을 즉시 할당할 수 있습니다.

예제 3.20. 연속적 값을 할당하기

>>> 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 모듈은 요일에 대하여 정수 상수를 정의합니다.)
이제 각 변수는 값이 있습니다: MONDAY0이고, TUESDAY1이며, 등등.

다중-변수 할당을 사용하여 여러 값을 돌려주는 함수를 만들 수도 있습니다. 그냥 모든 값을 터플로 돌려주면 됩니다. 호출자는 그것을 터플로 취급할 수 있으며 그 값들을 변수에 따로따로 할당할 수 있습니다. 많은 표준 파이썬 라이브러리에서 이렇게 합니다. 여기에는 os 모듈이 포함되는데 이 모듈은 제 6 장에서 다루겠습니다.

변수에 관하여 더 읽어야 할 것

3.5. 문자열 형식화

파이썬은 값을 문자열로 형식화하는 방법을 지원합니다. 여기에는 아주 복잡한 표현식이 포함될 수 있지만 대부분의 기본적인 사용법은 %s 위치보유자로 값을 문자열에 삽입하는 것입니다.

파이썬의 문자열 형식화는 구문이 Csprintf 함수와 똑 같습니다.

예제 3.21. 기본적인 문자열 형식화

>>> k = "uid"
>>> v = "sa"
>>> "%s=%s" % (k, v) 
'uid=sa'
전체 표현식은 문자열로 평가됩니다. 첫 %sk의 값으로 교체되고; 두번째 %sv의 값으로 교체됩니다. 다른 모든 문자들은 (이 경우, 등호 사인) 그대로 있습니다.

(k, v)는 터플임에 주의하세요. 터플은 쓸모가 있다고 말한 적이 있습니다.

단순한 문자열 결합에 이 정도는 너무 부담스럽다고 생각하실지 모르겠습니다. 문자열 형식화가 단순한 결합이 아니라는 점을 고려하지 않는다면 여러분의 생각이 옳을 수 있습니다. 단순히 형식화에 그치지 않고 유형도 강제로 변환합니다.

예제 3.22. 문자열 형식화 vs. 문자열 결합

>>> 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를 지정하면 정수와 작동합니다.
문자열을 문자열이 아닌 것과 결합하려고 하면 예외가 일어납니다. 문자열 형식화와 다르게 문자열 결합은 모든 것이 문자열일 경우에만 작동합니다.

Cprintf에서처럼 파이썬의 문자열 형식화는 스위스 군용 칼과 같습니다. 수 많은 다양한 유형의 값을 특별하게 형식화하기 위한 옵션이, 즉 장식 문자열이 풍부합니다.

예제 3.23. 숫자 형식화하기

>>> 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
%f 문자열 형식화 옵션은 값을 십진수로 취급하고 그것을 소수 6자리까지 인쇄합니다.
%f 옵션을 ".2"로 장식하면 값을 소수 두 자리까지 잘라냅니다.
장식을 조합해 쓸 수도 있습니다. + 장식을 추가하면 값 앞에 더하기나 빼기 사인을 표시합니다. ".2" 장식은 여전히 그 자리에 있으며 값을 정확하게 소수 두 자리까지 채우고 있음을 주목하세요.

3.6. 리스트 짝짓기

파이썬의 강력한 특징중 하나는 지능형 리스트인데 이는 한 리스트를 또다른 리스트에 간결하게 짝짓는 방법을 제공합니다. 리스트의 각 원소에 함수를 짝지으면 됩니다.

예제 3.24. 지능형 리스트 소개

>>> 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]
이를 이해하려면 오른쪽에서 왼쪽으로 읽어 보세요. li는 짝짓기 할 리스트입니다. 파이썬li를 한 번에 한 원소씩 회돌이하면서 임시로 각 원소의 값을 변수 elem에 할당합니다. 다음 파이썬은 함수 elem*2를 적용하고 그 결과를 반환된 리스트에 추가합니다.
지능형 리스트는 원본 리스트를 변경하지 않음에 주목하세요.
지능형 리스트의 결과를 짝짓기 할 변수에 할당하는 것이 안전합니다. 파이썬은 새로 리스트를 메모리에 구성하고 지능형 리스트가 완성되면 그 결과를 변수에 할당합니다.

다음은 제 2 장에 선언된 buildConnectionString 함수 안에 있는 지능형 리스트입니다:

["%s=%s" % (k, v) for k, v in params.items()]

먼저, params 사전의 items 함수를 호출하고 있음을 주목하세요. 이 함수는 사전의 모든 데이터를 터플에 넣어 리스트로 돌려줍니다.

예제 3.25.  keys 메쏘드와 values 메쏘드 그리고 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 사전의 키와 그의 연관 값을 담은 문자열이 됩니다.

예제 3.26. 단계별로 살펴 본 buildConnectionString의 지능형 리스트

>>> 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()와 동등하게 됩니다.
앞의 두 예제와 간단한 문자열 형식화를 조합하면 사전의 각 원소의 키와 값을 포함하는 문자열을 리스트로 얻을 수 있습니다. 이제 거의 우리 프로그램의 출력처럼 보입니다. 이 리스트의 원소들을 하나의 문자열로 결합하는 일만 남았습니다.

지능형 리스트에 관하여 더 읽어야 할 것

3.7. 문자열을 결합하고 분리하기

키-값 쌍을 key=value의 형태로 가지고 있고 그것을 하나의 문자열로 결합하고 싶습니다. 문자열 리스트를 하나의 문자열로 결합하려면 문자열 객체의 join 메쏘드를 사용합니다.

다음은 buildConnectionString 함수에서 리스트를 결합하는 예입니다:

    return ";".join(["%s=%s" % (k, v) for k, v in params.items()])

계속 읽기 전에 흥미로운 주의사항 하나가 있습니다. 함수도 객체이고, 문자열도 객체이며, ... 모든 것이 다 객체라고 반복해서 말했습니다. 어쩌면 문자열 변수도 객체라고 생각하실지 모르겠습니다. 그러나 아닙니다. 이 예제를 자세히 살펴보시면 문자열 ";"가 바로 객체이며 그의 join 메쏘드를 호출하고 있는 것을 알 수 있습니다.

join 메쏘드는 리스트의 원소들을 하나의 문자열로 결합합니다. 각 원소는 쌍반점으로 갈라서 말이지요. 구분자가 반드시 쌍반점일 필요는 없습니다; 심지어 한개짜리 문자일 필요조차 없습니다. 문자열이면 무엇이든 됩니다.

join 메쏘드는 문자열 리스트에만 작동합니다; 강제로 유형을 변환하지 않습니다. 문자열이 아닌 원소를 하나 이상 리스트로 결합하면 예외가 일어납니다.

예제 3.27.  odbchelper.py의 출력

>>> 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 메쏘드가 바로 그것입니다.

예제 3.28. 문자열 가르기

>>> 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']
split 메쏘드는 join 메쏘드와 반대로 문자열을 여러-원소의 리스트로 가릅니다. 가름자(“;”)가 완전히 잘려 나가고 없음을 주목하세요; 반환된 리스트의 어떤 원소에도 나타나지 않습니다.
split 메쏘드는 선택적으로 두 번째 인자를 받는데 이 인자는 가를 횟수를 지정합니다. (“"이런, 선택적 인자라...” 다음 장의 함수에서 이렇게 하는 방법을 배우겠습니다.)
anystring.split(delimiter, 1)는 문자열에서 부분문자열을 검색한 다음 그 부분문자열 앞에 있는 것(첫번째 원소)을 그리고 그 다음에 있는 것(두 번째 원소)을 처리할 때 유용한 테크닉입니다 .

3.7.1. 문자열 메쏘드에 숨은 역사

본인은 처음 파이썬을 배울 때 join이 리스트의 메쏘드가 되어 구분자를 인자로 받아야 한다고 기대했었습니다. 많은 사람들이 같은 생각이었는데 join 메쏘드 뒤에는 숨겨진 뒷이야기가 있습니다. 파이썬 1.6 이전에서 문자열은 이런 유용한 메쏘드들을 모두 갖추고 있지 못했습니다. 따로 string 모듈이 있었는데 여기에 모든 문자열 함수가 담겨 있었습니다; 함수마다 모두 문자열을 그의 첫 인자로 받았습니다. 함수들은 문자열 자체보다 우위에 둘 만큼 중요하게 여겨졌습니다. lowerupper 그리고 split과 같은 함수는 그 만한 의미가 있었습니다. 많은 파이썬 프로그래머가 이 새로운 join 메쏘드를 반대합니다. 대신에 리스트 메쏘드가 되거나 또는 아무데도 옮기지 말고 오히려 예전 string 모듈의 일부가 되는 편이 더 좋다고 주장합니다 (여전히 그 안에 유용한 것들이 수 없이 많습니다). 여기에서는 단독으로 새로운 join 메쏘드를 사용했지만 어느 방식으로 코드를 작성하셔도 상관없습니다. 정말 신경이 쓰인다면 대신에 예전의 string.join 함수를 사용하시면 됩니다.

3.8. 요약

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 장 강력한 내부검사 ☞