2장. 자료형과 참조

프로그래밍 언어와 애플리케이션은 데이터를 필요로 한다. 우리는 애플리케이션이 데이터를 가지고 일을 하도록 작성하며, 데이터를 담을 그릇container이 필요하다. 이 장에서는 컨테이너를 정의하여 애플리케이션 데이터와 함께 사용하는 데 관련된 모든 것을 다룬다. 자료가 키보드 입력을 통해 들어오든 혹은 데이터베이스를 사용하든, 우리의 프로그램이 자료를 조작하고 사용하기 위해서는 일시적으로 저장할 필요가 있다. 일단 데이터를 가지고 할 일을 마친 다음에는 이런 임시 컨테이너들은 새로운 구조를 위한 공간을 확보하기 위해 없애버릴 수 있다.

우리는 파이썬에서 제공하는 자료형을 살펴본 다음, 수집 및 저장한 자료를 어떻게 사용할 것인가에 대해 논의할 것이다. 우리의 수중에 있는 서로 다른 유형의 구조를 비교 대조할 것이며, 서로 다른 유형의 자료를 가지고 일하는 데에 쓰이는 구조에 대한 몇 가지 예제를 살펴볼 것이다. 목록, 사전, 튜플 등을 통해 수행할 수 있는 여러 가지 일들을 가능한 많이 다루어보도록 하겠다. 이러한 구조들을 어떻게 정의하고 사용하는지 배우고 난 후에는, 그것들이 더 이상 애플리케이션에서 필요하지 않을 때에 어떤 일이 일어날지에 대해서 이야기할 것이다.

파이썬 언어의 자료형과 자료구조를 살펴보는 여행을 시작하자. 어떤 자이썬 프로그램을 하든 간에 쓰이는 기술이다.

파이썬 자료형

논의하였던 것과 같이, 프로그램 내에서 데이터를 저장하고 조작할 필요가 있다. 그렇게 하기 위해서는 우리는 또한 프로그램이 사용할 수 있도록 데이터를 저장할 그릇을 만들 능력이 있어야 한다. 언어는 데이터가 저장되면 어떻게 다루어야할지 알 필요가 있으며, 자바에서는 그릇에 자료형을 할당함으로써 그렇게 할 수 있다. 그렇지만 파이썬에서는 어떤 유형의 데이터를 저장하는지 번역기가 동적인 방식으로 판단하기 때문에 그렇게 할 필요가 없다.

표 2-1에서 각각의 자료형의 성질에 대한 간략한 설명을 나열한다.

표 2-1. 파이썬의 데이터 유형

자료형 설명
없음(None) NULL 값 개체
정수(int) 일반 정수 (예 : 32)
정수(long) 긴 정수. 일반 정수로 쓰기에 너무 긴 정수로서, 접두사 ‘L’과 함께 사용
부동소수점수(float) 부동소수점수. 10진수 또는 exponent sign을 포함하는 숫자
복소수(complex) 복소수. 실수와 허수부의 합으로 된 숫자로 표현
부울(Boolean) 참(True) 또는 거짓(False) 값 (1과 0의 숫자값으로도 표현)
순서(Sequence) 다음과 같은 유형을 포함: 문자열, 유니코드 문자열, basestring, 리스트, 튜플
매핑(Mapping) 사전(dictionary) 형을 포함
집합(Set) 정렬되지 않은 유일한 개체의 모음으로서, 다음과 같은 유형을 포함: set, frozenset
파일(File) 파일 시스템 개체를 사용할 수 있도록 하는데 사용
반복자(Iterator) 컨테이너에 대하여 되풀이. 자세한 내용은 반복자 절을 참조

모든 정보와 위의 예제를 감안할 때, 파이썬 언어에서 변수를 선언하는 방법을 공식적으로 논의함이 마땅하다. 다음의 코드에서 변수를 정의하는 몇 가지 예를 살펴보도록 하자.

예제 2-1. 자이썬에서 변수 정의하기

# 문자열을 정의
x = 'Hello World'
x = "Hello World Two"

# 정수를 정의
y = 10

# 부동소수점수
z = 8.75

# 복소수
i = 1 + 8.07j

명심하여야할 것은 자이썬에서는 실제로는 형이 존재하지 않는다는 것이다. 모든 개체는 클래스의 인스턴스이다. 개체의 형을 찾으려면, 단순히 type() 함수를 사용한다.

예제 2-2.

>>> # type 함수를 사용하여 개체의 유형을 반환
>>> i = 1 + 8.07j
>>> type(i)
<type 'complex'>
>>> a = 'Hello'
>>> type(a)
<type 'str'>

알아둘 만한 훌륭한 기능으로 다중 할당이 있다. 여러 변수에 각각 숫자값을 할당해야할 때가 종종 있는데, 파이썬에서는 다중 할당을 사용하여 한 줄 안에서 처리할 수 있다.

예제 2-3. 다중 할당

>>> x, y, z = 1, 2, 3
>>> print x
1
>>> print z
3

문자열과 문자열 메소드

문자열은 데이터를 조작하는 데 자주 쓰이기 때문에 대부분의 프로그래밍 언어에서 특별한 유형이다. 파이썬에서의 문자열은 일련의 문자로서, 불변immutable이다. 불변의 개체란 생성한 후에는 변경할 수 없는 것이다. 그 반대는 가변mutable 개체로서, 생성된 이후에 바뀔 수 있는 것이다. 문자열을 전반적으로 이해하는 데 있어 큰 부분을 차지하므로 이러한 점을 이해하는 것이 매우 중요하다. 그러나, 특정한 문자열의 내용을 조작하는 데 쓰이는 문자열 메소드가 제법 있다. 우리가 내용을 실제로 조작하는 것은 절대로 아니며, 이런 메소드들은 문자열을 조작한 사본을 반환한다. 원본 문자열은 바뀌지 않는다.

자이썬 2.5.0 판 이전에는, CPython과 자이썬 사이에는 문자열을 다루는 방식에 있어 약간의 차이가 있었다. CPython에서 문자열 개체는 두 가지 유형이 있는데, 이것들은 표준 문자열과 유니코드 문자열이라고 알려져있다. 두 유형의 문자열의 차이점에 초점을 맞춘 많은 문서가 있으므로, 여기서는 기본적인 부분만 다룰 것이다. 파이썬에는 basestring이라고 하는 추상 문자열 형이 있어서 임의의 유형의 문자열이 문자열 인스턴스임을 확신할 수 있도록 확인할 수 있다는 점은 알아둘 만한 가치가 있다.

자이썬 2.5.0 판 이전에는 한 가지 종류의 문자열 밖에 없었다. 자이썬의 문자열 형은 2바이트 유니코드 문자 전체와 유니코드를 인식하는 문자열 모듈에 있는 모든 함수를 지원한다. u’’ 문자열 수정자를 지정하면, 자이썬은 그것을 무시한다. 2.5.0 판부터, 자이썬에서도 문자열을 CPython에서와 같이 취급하므로, 양쪽 구현에서 동일한 규칙이 적용된다. 문자열 인코딩에 대해 더 알고 싶으면, 그 주제를 다루는 다른 좋은 자료를 참고하기 바란다. 자이썬이 자바 플랫폼의 문자 메소드를 사용한다는 점도 알아두는 것이 좋다. 나중에 논의할 isupper와 islower와 같은 속성은, CPython의 대응되는 것들과 같은 방식으로 동작하기는 하지만, 자바 메소드에 기초한다.

이 절의 나머지에서, 우리가 원하는 대로 쓸 수 있는 많은 문자열 함수 각각을 살펴볼 것이다. 이 함수들은 표준 및 유니코드 문자열 양쪽에서 동작한다. 파이썬 및 다른 프로그래밍 언어에서 많은 다른 기능처럼, 가끔은 일을 완수하는 데에 여러 방법이 있을 수 있다. 문자열과 문자열 조작의 경우에는 이것이 참이다. 하지만, 대부분의 경우에 알게 되겠지만, 뭔가를 하는 데 여러 방법이 있다하더라도, 파이썬 전문가들이 보다 나은 성능을 내고 코드를 읽기 쉽게 하는 함수들을 추가하였다.

표 2-2에는 파이썬 2.5 판에 내장된 문자열 메소드를 모두 나열하였다. 파이썬은 진화하는 언어이므로, 이 목록은 향후에 변경될 것이 틀림없다. 언어에 추가가 일어나거나, 기존 기능이 향상되는 일이 빈번하다. 표에 따라, 메소드와 그 사용법에 대한 많은 예를 들 것이다. 이런 메소드들 각각이 어떻게 동작하는지에 대한 예제는 제공할 수 없지만(그 자체로 책이 나올 것이다), 그것들은 같은 방식으로 쓰이므로 선택에는 어려움이 없을 것이다.

표 2-2. 문자열 메소드

메소드 기능 설명
capitalize() 문자열의 첫 글자를 대문자로 하는 사본을 반환
center (width[,fill]) 지정된 넓이와 선택사항인 채움 문자로 위치가 재조정된 문자열
count(sub[,start[,end]]) 문자열 내에서 부분문자열이 발생하는 횟수를 셈
decode([encoding[,errors]]) 디코드하여 유니코드 문자열을 반환
encode([encoding[,errors]]) 인코드된 버전의 문자열을 반환
endswith(suffix[,start[,end]]) 문자열이 주어진 형태로 끝나는지의 여부를 반환
expandtabs([tabsize]) 탭을 빈 칸 문자열로 바꿈
find(sub[,start[,end]]) 주어진 문자열 조각이 처음 나타나는 곳의 인덱스를 반환
index(sub[,start[,end]) 주어진 문자열 조각이 처음으로 나타나는 인덱스를 반환. 문자열 조각을 찾지 못하면 ValueError를 일으킴.
isalnum() 문자열이 영문자와 숫자로만 이루어졌는지의 여부를 반환
isalpha() 문자열이 영문자로만 이루어졌는지의 여부를 반환
isdigit() 문자열이 숫자로만 이루어졌는지의 여부를 반환
islower() 문자열이 소문자로만 이루어졌는지의 여부를 반환
isspace() 문자열이 공백으로만 이루어졌는지의 여부를 반환
istitle() 문자열의 각 단어마다 첫글자가 대문자로 되어있는지의 여부를 반환
isupper() 문자열 내의 모든 글자가 대문자인지의 여부를 반환
join(sequence) 원본 문자열이 각 원소 사이에 배치되도록 이어붙인 순서형의 사본 문자열을 반환
ljust(width[,fillchar]) 원본 문자열의 가장 왼쪽부분을 정해진 폭에 맞춘 문자열을 반환(빈 자리를 fillchar로 채우도록 선택 가능)
lower() 원본 문자열의 모든 글자를 소문자로 바꾼 사본을 반환
lstrip([chars]) 주어진 글자들을 문자열의 왼쪽에서부터 찾아서 처음으 로 일치하는 글자들을 제거. 또한 왼쪽 공백을 제거. 인수를 지정하지 않았을 때는 공백 제거를 기본값으로 함.
partition(separator) 주어진 분리기호(separator)를 사용하여 왼쪽으로부터 분할한 문자열을 반환
replace(old,new[,count]) 원본 문자열에서 old로 주어진 부분을 new로 주어진 부분으로 치환한 사본을 반환
rfind(sub[,start[,end]]) 문자열을 오른쪽으로부터 왼쪽으로 검색하여 주어진 문자열이 처음으로 나타나는 것을 찾아 sub를 찾은 곳의 가장 높은 자리수를 반환
rindex(sub[,start[,end]]) 문자열을 오른쪽으로부터 왼쪽으로 검색하여 주어진 문자열이 처음으로 나타나는 것을 찾아 sub를 찾은 곳의 가장 높은 자리수를 반환하거나 예외를 제기
rjust(width[,fillchar]) width에 맞추어 오른쪽으로 정렬된 문자열 사본을 반환
rpartition(separator) 주어진 구분자 개체를 사용하여 오른쪽으로부터 시작하여 분할한 문자열 사본을 반환
rsplit([separator[,maxsplit]]) 문자열로 된 단어의 목록을 반환하며 문자열을 오른쪽으로부터 쪼개며 주어진 분리 기호(separator)를 구분자로 사용함. 최대분할(maxsplit)이 지정된 경우 (오른쪽으로부터) 최대 maxsplit이 이루어짐
rstrip([chars]) 주어진 문자들과 일치하는 것을 오른쪽으로부터 찾아서 처음 찾은 문자들을 제거한 문자열 사본을 반환. 또한 인수를 지정하지 않은 경우에는 오른쪽으로부터 공백을 제거.
split([separator[,maxsplit]]) 문자열 내의 단어 목록을 반환하며 주어진 분리자를 구 분자로 하여 문자열을 왼쪽에서부터 쪼갠다.
splitlines([keepends]) 문자열을 행 목록으로 분할. 줄 바꿈 기호를 제거하는 경우 keepends가 나타남. 문자열에서 행 목록을 반환.
startswith(prefix[,start[,end]]) 문자열이 주어진 prefix로부터 시작하는지의 여부를 반 환
strip([chars]) 문자열에서 주어진 문자들을 제거한 문자열 사본을 반 환. 인수를 지정하지 않으면 공백이 제거됨.
swapcase() 각 문자의 대소문자가 뒤바뀐 문자열 사본을 반환.
title() 각 단어의 첫 글자를 대문자로 하는 문자열의 사본을 반 환.
translate(table[,deletechars]) 문자열을 번역하기 위해 주어진 문자 번역표를 사용하여 문자열 사본을 반환. 주어진 deletechars 선택 인수에 나타나는 모든 문자는 제거됨.
upper() 문자열의 모든 글자를 대분자로 변환한 문자열 사본을 반환
zfill(width) 주어진 폭에 맞추어 왼쪽에 0을 채운 숫자 문자열을 반 환.

몇 가지 예를 살펴보면 문자열 메소드를 어떻게 써야할 지 감이 올 것이다. 앞서 기술한 바와 같이, 그것들의 대부분이 비슷한 방식으로 작동한다.

예제 2-4. 문자열 메소드 사용하기

>>> our_string='python is the best language ever'

>>> # 문자열의 첫 글자를 대문자로
>>> our_string.capitalize()
'Python is the best language ever'

>>> # 가운데 정렬
>>> our_string.center(50)
'         python is the best language ever         '
>>> our_string.center(50,'-')
'---------python is the best language ever---------'

>>> # 문자열 내에서 부분문자열 세기
>>> our_string.count('a')
2

>>> # 부분문자열 발생 횟수 세기
>>> state = 'Mississippi'
>>> state.count('ss')
2

>>> # 문자열을 분할(partition)하면, 분리자의 앞 부분, 분리자, 그리고 그 분리자의
>>> # 뒷 부분의 세 원소로 이루어진 튜플을 반환
>>> x = "Hello, my name is Josh"
>>> x.partition('n')
('Hello, my ', 'n', 'ame is Josh')

>>> # 위와 똑같은 x가 있다고 할 때, 'l'을 분리자로 하여 문자열을 쪼개기(split)
>>> x.split('l')
['He', '', 'o, my name is Josh']

>>> # 보는 바와 같이 분리자는 반환되는 목록에 포함되지 않는다.
>>> # maxsplits 값을 1로 하여 추가하게 되면, 가장 왼쪽부터 쪼개기가 이루어진다.
>>> # maxsplits 값을 2로 주면, 가장 왼쪽 두 개의 쪼개기가 이루어진다.
>>> x.split('l',1)
['He', 'lo, my name is Josh']
>>> x.split('l',2)
['He', '', 'o, my name is Josh']

문자열 서식

print 문을 사용하여 문자열을 출력할 때에는 많은 선택사항이 있다. C 프로그래밍 언어와 흡사하게, 파이썬의 문자열 서식을 통해 여러 형태로 변환하여 출력할 수 있다.

예제 2-5. 문자열 서식 사용하기

>>> # 아래의 두 구문은 동일하게 작동한다
>>> x = "Josh"
>>> print "My name is %s" % (x)
My name is Josh
>>> print "My name is %s" % x
My name is Josh

>>> # 하나 이상의 인수를 사용하는 예제
>>> name = 'Josh'
>>> language = 'Python'
>>> print "My name is %s and I speak %s" % (name, language)
My name is Josh and I speak Python

>>> # 좀 더 재미있는, 또 다른 변환 형태가 있다
>>> # 세상 어디가 그렇게 온도 변화가 심한지 모르겠다.
>>> day1_temp = 65
>>> day2_temp = 68
>>> day3_temp = 84
>>> print "Given the temparatures %d, %d, and %d, the average would be %f" % (day1_temp, day2_temp, day3_temp, (day1_temp + day2_temp + day3_temp)/3)
Given the temperatures 65, 68, and 83, the average would be 72.333333

표 2-3에서 변환 유형을 나열한다.

표 2-3. 변환 유형

종류 설명
d 부호가 있는 10진수
i 부호가 있는 정수
o 부호 없는 8진수
u 부호 없는 10진수
x 부호 없는 16진수 (소문자)
X 부호없는 16진수 (대문자)
E 부동소수점 지수 형식 (대문자 ‘E’)
e 부동소수점 지수 형식 (소문자 ‘e’)
f 부동소수점 십진수 형식 (소문자)
F 부동소수점 십진수 형식 (‘f’와 동일)
g 지수 < -4 일 경우 부동소수점 지수 형식, 그렇지 않으면 부동소수점수
G 지수 < -4일 경우 부동소수점 지수 형식(대문자), 그렇지 않으면 부동소수점수
c 단일 문자
r 문자열 (임의의 파이썬 개체를 repr()를 사용하여 변환)
s 문자열 (임의의 파이썬 개체를 str()을 사용하여 변환)
% 두번 쓰일 경우, 변환 없이 백분율(%) 문자를 반환

예제 2-6.

>>> x = 10
>>> y = 5.75
>>> print 'The expression %d * %f results in %f' % (x, y, x*y)
The expression 10 * 5.750000 results in 57.500000

>>> # 백분율 사용 예제
>>> test1 = 87
>>> test2 = 89
>>> test3 = 92
>>> "The gradepoint average of three students is %d%%" % (avg)
'The gradepoint average of three students is 89%'

목록, 사전, 집합, 그리고 튜플

목록, 사전, 집합, 그리고 튜플은 모두 비슷한 기능과 사용법을 제공하지만, 각각은 언어에서의 자신만의 쓰임새를 가지고 있다. 그것들은 모두 특정한 상황에서 중요한 역할을 하기 때문에 각각에 대한 예제를 여러개 살펴 볼 것이다. 문자열과는 달리, 이 절에서 논의하는 (튜플을 제외한) 모든 컨테이너들은 변경 가능한 개체이므로, 생성된 이후에 조작을 가할 수 있다.

이러한 컨테이너들은 매우 중요하기 때문에, 이 장의 말미에는 연습문제를 통해 여러분이 직접 그것들을 써볼 수 있도록 할 것이다.

목록

파이썬 프로그래밍 언어 내에서 가장 많이 쓰이는 구조는 바로 목록list일 것이다. 다른 많은 프로그래밍 언어는 애플리케이션 내에서 데이터를 저장하고 조작하기 위한 유사한 컨테이너를 제공한다. 파이썬의 목록은 정적 유형 언어에서 제공하는 유사한 구조들에 비해 더욱 장점을 제공한다. 파이썬 언어의 동적인 경향은 목록으로 하여금 서로 다른 유형의 값들을 담을 수 있는 능력이라는 훌륭한 기능을 구축하도록 도와준다. 다시 말해 목록은 어떠한 파이썬 자료형이라도 저장하는데 쓰일 수 있으며, 단일한 목록 내에 이러한 유형을 섞을 수도 있다. 다른 언어에서는, 이런 종류의 구조는 형을 가진 개체로 정의하며, 한 가지 자료형만을 사용하도록 구조를 제한하는 경우가 많다.

파이썬 언어에서는 목록의 생성과 사용이 매우 단순하며 용이하다. 그저 빈 대괄호 한 쌍을 변수에 할당하면 빈 목록이 생성되며, 내장된 list() 함수를 사용하여 목록을 생성할 수도 있다. 목록은 애플리케이션을 수행하는 중에 생성 및 수정할 수 있으며, 고정된 길이로 선언하지 않는다. 그것들은 되풀이 용법을 위해 순회하기 쉬우며, 특정 항목을 정해진 위치에 배치하거나 삭제하고자 할 때는 인덱스를 사용할 수도 있다. 목록을 정의하는 몇가지 예제를 보여주는 것으로 시작해서, 목록을 다룰 수 있도록 파이썬 언어에서 제공하는 각각의 방법으로 넘어가보도록 하겠다.

예제 2-7. 목록 정의하기

>>> # 빈 목록을 정의
>>> my_list = []
>>> my_list = list()  # 가끔 사용됨

>>> # 하나의 원소를 가진 목록
>>> my_list = [1]
>>> my_list           # 번역기에서 변수를 출력하기 위해서 print를 사용할 필요가 없다
[1]

>>> # 문자열 값의 목록을 정의
>>> my_string_list = ['Hello', 'Jython' ,'Lists']

>>> # 복수의 자료형을 포함하는 목록을 정의
>>> multi_list = [1, 2, 'three', 4, 'five', 'six']

>>> # 목록을 포함하는 목록을 정의
>>> combo_list = [1, my_string_list, multi_list]

>>> # 목록을 포함하는 목록을 한 줄로 정의
>>> my_new_list = ['new_item1', 'new_item2', [1, 2, 3, 4], 'new_item3']
>>> print my_new_list
['new_item1', 'new_item2', [1, 2, 3, 4], 'new_item3']

앞서 기술한 바와 같이, 목록에서 값을 얻기 위해 인덱스를 사용할 수 있다. 자바 언어에서의 배열과 흡사하게, list[인덱스] 구문을 사용하여 항목에 접근할 수 있다. 목록으로부터 범위나 값의 집합을 얻고자 한다면, 시작 인덱스 및 끝 인덱스를 지정할 수 있다. 이러한 기법은 썰기slicing라고 알려져 있다. 부가적으로, 단계 인덱스를 제공함으로써 목록으로부터 값을 띄엄띄엄 모은 집합을 반환할 수도 있다. 기억해야할 열쇠는 인덱스를 통해 목록에 접근할 때에는, 목록에 저장된 첫 원소의 인덱스는 0이라는 점이다. 목록을 썰면, 항상 새로운 목록이 반환된다는 점에 유의하라. 목록에 대한 얕은 복사shallow copy를 생성하는 한 가지 방법은 썰기 구문을 쓸 때 상한이나 하한을 지정하지 않는 것이다. 하한의 기본값은 0이며, 상한의 기본값은 목록의 길이가 된다.

얕은 복사는 새로운 복합 개체(목록 또는 개체를 포함하는 다른 개체)를 생성한 다음에 원본 개체에 참조를 삽입한다는 점에 유의하라. 깊은 복사deep copy는 새로운 복합 개체를 생성한 다음, 원본에 사본을 삽입한다.

예제 2-8. 목록에 접근하기

>>> # 목록에서 원소를 얻기
>>> my_string_list[0]
'Hello'
>>> my_string_list[2]
'Lists'

>>> # 음수 인덱스는 목록의 마지막 원소에서 시작하여 첫 항목 쪽으로 거슬러 감
>>> my_string_list[-1]
'Lists'
>>> my_string_list[-2]
'Jython'

>>> # 썰기(썰기는 시작 인덱스의 원소는 포함하고 끝 인덱스의 원소는 제외한다는 점에 유의)
>>> my_string_list[0:2]
['Hello', 'Jython']

>>> # 썰기를 통해 목록의 얕은 사본을 생성
>>> my_string_list_copy = my_string_list[:]

>>> my_string_list_copy
['Hello', 'Jython', 'Lists']

>>> # 목록에서 모든 다른 원소를 반환
>>> new_list=[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

>>> # 썰기에서 세번째 인자를 사용하면 건너뛰기가 됨
>>> # 이 예제는 한 칸 씩 이동(step)
>>> new_list[0:10:1]
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

>>> # 그리고 여기서는 두 칸 씩 이동
>>> new_list[0:10:2]
[2, 6, 10, 14, 18]

>>> # 시작 인덱스를 비워두면 기본값이 0을 지정한 것과 같이 동작하며, 끝 인덱스에 대해서는 목록의 길이가 기본값임
>>> new_list[::2]
[2, 6, 10, 14, 18]

목록을 수정하는 것은 더 비슷한데, 항목을 특정한 위치에 삽입하거나 제거하기 위해 인덱스를 사용할 수 있다. 목록에서 항목을 추가하거나 삭제하는 다른 많은 방법이 있다. 파이썬은 연산에 필요한 서로 다른 기능을 제공하는 것과 같이 다양한 옵션을 제공한다.

예제 2-9.

>>> # 목록의 원소를 수정. 이 경우는 9번 위치에 있는 원소를 수정함
>>> new_list[9] = 25
>>> new_list
[2, 4, 6, 8, 10, 12, 14, 16, 18, 25]

목록의 끝에 항목을 추가하기 위해 append() 메소드를 사용할 수 있다. extend() 메소드는 목록의 끝에 목록이나 순서 전체의 사본을 추가할 수 있도록 해준다. 마지막으로, insert() 메소드는 한 항목이나 다른 목록을 기존 목록의 특정한 위치에 인덱스를 활용하여 배치할 수 있도록 해준다. 기존의 목록에 다른 목록이 삽입되면 그것은 원본 목록과 결합되는 것이 아니라, 원본 목록 내에 들어있는 독립된 항목이 된다. 각 메소드의 예제를 아래에서 찾을 수 있다.

마찬가지로, 우리는 목록에서 항목을 제거하기 위한 많은 옵션이 있다. 1장에서 설명한 del 문이, 인덱스 표기법을 통해 목록으로부터 전체 목록 또는 값들을 제거 또는 삭제하는데 쓰일 수 있다. 또한 목록에서 단일 값을 제거하는 pop() 또는 remove() 메소드를 사용할 수 있다. pop() 메소드는 목록의 끝에서 단일 값을 제거하며, 또한 동시에 그 값을 반환한다. pop()에 인덱스가 인자로 주어지면, 해당 위치에 있는 값을 제거 및 반환한다. remove() 메소드는 목록에서 특정 값을 찾아서 지우는데 사용할 수 있다. 다시 말해, remove()는 처음 일치하는 원소를 목록에서 제거한다. remove() 함수로 전달한 값과 하나 이상의 값이 일치하면, 처음 것이 제거될 것이다. remove()에 대한 다른 주의점은 제거된 값은 반환하지 않는다는 점이다. 목록을 수정하는 이러한 예제들을 살펴보자.

예제 2-10. 목록 수정하기

>>> # append 메소드를 사용하여 목록에 값들을 추가
>>> new_list=['a','b','c','d','e','f','g']
>>> new_list.append('h')
>>> print new_list
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

>>> # 다른 목록을 기존 목록에 추가
>>> new_list2=['h','i','j','k','l','m','n','o','p']
>>> new_list.extend(new_list2)
>>> print new_list
['a', 'b', 'c', 'd', 'e', 'f', 'g', ‘h’,'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']

>>> # 인덱스를 통해 특정 위치에 값을 삽입.
>>> # 이 예제에서는 'c'를 목록에서 3 번째 위치에 추가
>>> # (목록의 인덱스는 0부터 시작하므로, 인덱스 2가 실제로는 세번째 자리를 가리킴을 기억하라)
>>> new_list.insert(2,'c')
>>> print new_list
['a', 'b', 'c', 'c', 'd', 'e', 'f', 'g', 'h', ‘h’,'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']

>>> # 인덱스를 통해 특정한 위치로 목록을 삽입
>>> another_list = ['a', 'b', 'c']
>>> another_list.insert(2, new_list)
>>> another_list
['a', 'b', [2, 4, 8, 10, 12, 14, 16, 18, 25], 'c']

>>> # 썰기 표기법을 사용하여 목록 또는 순서의 일부를 덮어씀
>>> new_listA=[100,200,300,400]
>>> new_listB=[500,600,700,800]
>>> new_listA[0:2]=new_listB
>>> print new_listA
[500, 600, 700, 800, 300, 400]

>>> # 빈 썰기 표기법을 사용하여 목록을 다른 목록에 할당
>>> one = ['a', 'b', 'c', 'd']
>>> two = ['e', 'f']
>>> one
['a', 'b', 'c', 'd']
>>> two
['e', 'f']

>>> # 시작과 종료 위치를 동일하게 하여 목록으로부터 빈 조각을 만들어낸다.
>>> # 시작과 끝 위치를 동일하게 쓰는 한, 어느 위치든 상관 없음.
>>> one[2:2]
[]

>>> # 그 자체로는 별로 재미가 없다 - 빈 목록은 아주 쉽게 만들 수 있다.
>>> # 이것에 대해 유용한 점은 이것을 빈 조각에 할당할 수 있다는 것이다.
>>> # 이제, 'two' 목록을 'one'목록의 빈 조각에 할당함으로써 실제로는 'two' 목록을 'one' 목록에 삽입한다.
>>> one[2:2] = two          # 'one' 목록의 원소 1과 2 사이를 'two' 목록으로 대체
>>> one
['a', 'b', 'e', 'f', 'c', 'd']

>>> # del 구문을 사용하여 값 또는 값의 범위를 목록으로부터 제거
>>> # 모든 다른 원소가 이동하여 빈 공간을 채움에 유의
>>> new_list3 = ['a','b','c','d','e','f']
>>> del new_list3[2]
>>> new_list3
['a', 'b', 'd', 'e', 'f']
>>> del new_list3[1:3]
>>> new_list3
['a', 'e', 'f']

>>> # del 구문을 사용하여 목록을 제거
>>> new_list3=[1,2,3,4,5]
>>> print new_list3
[1, 2, 3, 4, 5]
>>> del new_list3
>>> print new_list3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'new_list3' is not defined

>>> # pop과 remove 함수를 사용하여 목록으로부터 값을 제거
>>> print new_list
['a', 'b', 'c', 'c', 'd', 'e', 'f', 'g', 'h',’h’, 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']

>>> # 인덱스가 2인 원소를 pop
>>> new_list.pop(2)
'c'
>>> print new_list
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',’h’, 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']

>>> # 목록에서 처음으로 나타나는 'h' 문자를 제거
>>> new_list.remove('h')
>>> print new_list
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']

>>> # pop() 함수를 사용하는 유용한 예제
>>> x = 5
>>> times_list = [1,2,3,4,5]
>>> while times_list:
...     print x * times_list.pop(0)
...
5
10
15
20
25

이제 목록에 항목을 추가하고 제거하는 방법을 알았으니, 자료를 어떻게 조작하는지 배우도록 하자. 파이썬은 목록을 관리하는 데에 도움이되는 많은 수의 메소드를 제공한다. 표 2-4에서 함수들을 가지고 무엇을 할 수 있는지 살펴보라.

표 2-4. 파이썬 목록 메소드

메소드 하는 일
index 주어진 값과 일치하는, 목록 내의 첫 번째 값을 반환
count 주어진 값과 동등한, 목록 내의 원소의 갯수를 반환
sort 목록 내에 포함된 원소를 정렬하여 그 목록을 반환
reverse 목록 내에 포함된 원소를 역순으로 배열하여 그 목록을 반환

이러한 함수들을 목록에 대하여 어떻게 사용할 수 있는지 살펴보자.

예제 2-11. 목록 함수 활용하기

>>> # 어떤 주어진 값에 대한 인덱스를 반환
>>> new_list=[1,2,3,4,5,6,7,8,9,10]
>>> new_list.index(4)
3

>>> #  인덱스가 4인 원소의 값을 변경
>>> new_list[4] = 30
>>> new_list
[1, 2, 3, 4, 30, 6, 7, 8, 9, 10]

>>> # 이제, 값을 원상태로 되돌려놓자.
>>> new_list[4] = 5
>>> new_list
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> # 이 목록에 중복된 값을 추가하고 인덱스를 반환
>>> # 처음 나타나는 값의 인덱스를 반환함에 유의
>>> new_list.append(6)
>>> new_list
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 6]
>>> new_list.index(6)
5

>>> # 주어진 값과 동등한 원소의 갯수를 반환하도록 count() 함수를 사용
>>> new_list.count(2)
1
>>> new_list.count(6)
2

>>> # 목록의 값들을 정렬
>>> new_list.sort()
>>> new_list
[1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10]

>>> # 목록의 값들의 순서를 반대로
>>> new_list.reverse()
>>> new_list
[10, 9, 8, 7, 6, 6, 5, 4, 3, 2, 1]

목록 순회와 탐색

목록 내에서 이동하는 것은 꽤 단순하다. 목록에 일단 원소가 채워지면, 그 목록을 순회하며 목록 내의 각 원소에 대하여 어떤 행위를 수행하고 싶을 때가 종종 있다. 목록 내의 각 원소를 순회하기 위해서 파이썬의 되풀이 구조 중 무엇이든 사용할 수 있다. 많은 선택사항이 있겠지만, for 되풀이의 쓰임새가 특히 좋다. 그 이유는 되풀이를 사용하는 파이썬의 간단한 구문 때문이다. 이 장에서는 파이썬의 서로 다른 되풀이 구조를 사용하여 목록을 어떻게 순회하는지를 보여줄 것이다. 각각의 방법에는 장단점이 있다.

우선 살펴볼 것은 for 되풀이를 사용하여 목록을 순회하는 구문이다. 목록 내에 포함된 각 원소를 돌아다니는 데에 있어 가장 쉬운 방법이다. for 되풀이는 목록의 원소를 한번에 하나씩 순회하여, 개발자로 하여금 각 원소에 대하여 원하는 행위를 수행할 수 있도록 해준다.

예제 2-12. ‘for’ 되풀이를 사용하여 목록을 순회

>>> ourList=[1,2,3,4,5,6,7,8,9,10]
>>> for elem in ourList:
...    print elem
...
1
2
3
4
5
6
7
8
9
10

이 간단한 예제에서 볼 수 있듯이, 목록을 훑어나가면서 각각의 원소에 대하여 작업하는 것은 아주 쉽다. 이 for 루프 구문은 루프를 통과할 때마다 목록의 각 원소가 할당되는 변수를 필요로 한다.

썰기(slicing)와 for 되풀이를 결합하는 것도 가능하다. 이 경우에는, 우리가 보고 싶은 원소를 추출하기 위해 그저 목록의 일부를 사용하면 된다. 목록에서 처음 다섯 개의 원소를 순회하는 다음의 예제를 보라.

예제 2-13.

>>> for elem in ourList[:5]:
...     print elem
...
1
2
3
4
5

보는 바와 같이, 파이썬에서 제공하는 내장된 기능을 그저 사용하면 되기 때문에 꽤 쉽다.

목록 함축

앞의 절에서 본 것과 같이, 썰기를 통해서 목록의 사본을 만들 수 있다. 목록의 사본을 만드는 또다른 강력한 방법으로 목록 함축list comprehension을 들 수 있다. 목록과 관련하여 개발자의 삶을 보다 편하게 만드는 데 도움이 되는 몇가지 고급 기능이 있는데, 그 중 하나가 목록 함축이라고 알려진 기능이다. 이 개념이 처음에는 어려울 수도 있지만, 독립된 목록을 수동으로 많이 만드는 일의 좋은 대안을 제공한다. 목록 함축은 주어진 목록을 받은 다음, 그것들을 순회하여 주어진 표현식을 목록의 각 개체에 적용한다.

예제 2-14. 간단한 목록 함축

>>> # 목록 함축을 사용하여 목록에서 각각에 숫자에 2를 곱함
>>> # 목록 함축은 새로운 목록을 반환함에 유의
>>> num_list = [1, 2, 3, 4]
>>> [num * 2 for num in num_list]
[2, 4, 6, 8]

>>> # 목록 함축을 변수에 할당할 수 있다
>>> num_list2 = [num * 2 for num in num_list]
>>> num_list2
[2, 4, 6, 8]

볼 수 있는 것과 같이, 재빨리 목록을 취하여 주어진 식을 사용하여 변경할 수 있다. 물론 다른 여러 파이썬 메소드와 같이 목록 함축은 목록의 변경된 사본을 반환한다. 목록 함축은 새로운 목록을 생산하며 원본 목록은 온전히 남아 있다.

목록 함축의 구문을 살펴보자. 그것들은 기본적으로 어떤 종류의 식 뒤에 for 문과 선택적인 for나 if 문이 따라오도록 구성된다. 목록 함축의 기본 기능은 목록의 항목을 돌면서, 목록의 각 구성원에 어떤 식을 적용하는 것이다. 문법적으로, 목록 함축은 다음과 같이 읽힌다.

목록을 반복하며 각 원소에 선택적으로 수행한 다음, 결과 요소를 포함하는 새로운 목록을 반환하거나 선택적인 조항으로 각 원소를 평가한다.

[원소 (옵션 식) for 원소 in 목록 (옵션 절)]

예제 2-15. 목록 함축에서 if 절 사용하기

>>> # 다음은 목록에서 숫자 4보다 큰 원소를 반환하는 예이다.
>>> nums = [2, 4, 6, 8]
>>> [num for num in nums if num > 4]
[6, 8]

예제를 좀 더 살펴보자. 목록 함축이 실제로 어떻게 쓰이는지 일단 보고 나면, 그것이 얼마나 유용하게 쓰일 수 있는지 알게 될 것이다.

예제 2-16. 파이썬 목록 함축

>>> # 목록 함축을 이용하여 나이의 목록을 생성하고 각각에 1을 더함
>>> ages=[20,25,28,30]
>>> [age+1 for age in ages]
[21, 26, 29, 31]

>>> # 이름 목록을 생성하고 각 이름의 첫 글자를 대문자로 변환
>>> names=['jim','frank','vic','leo','josh']
>>> [name.title() for name in names]
['Jim', 'Frank', 'Vic', 'Leo', 'Josh']

>>> # 숫자 목록을 만들고 그 중 짝수에 대해서만 각각의 제곱을 반환
>>> numList=[1,2,3,4,5,6,7,8,9,10,11,12]
>>> [num*num for num in numList if num % 2 == 0]
[4, 16, 36, 64, 100, 144]

>>> # 목록 함축을 범위와 함께 사용
>>> [x*5 for x in range(1,20)]
[5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]

>>> # for 문을 사용하여 서로 다른 두 목록의 원소에 대해 계산을 수행
>>> list1 = [5, 10, 15]
>>> list2 = [2, 4, 6]
>>> [e1 + e2 for e1 in list1 for e2 in list2]
[7, 9, 11, 12, 14, 16, 17, 19, 21]

목록 함축은 코드를 보다 간결하게 만들어주며 보다 쉽게 목록의 원소에 식이나 함수를 적용할 수 있도록 해준다. 목록 함축과 똑같은 일을 수행하는 자바로 쓰여진 예제를 잠깐 살펴보자. 목록 함축이 훨씬 간결하다는 것을 확인할 수 있다

예제 2-17. 나이 목록을 취하여 각각에 1을 더하는 자바 코드

int[] ages = {20, 25, 28, 30};
int[] ages2 = new int[ages.length];

// 배열의 각 원소를 돌기 위해 자바의 for 되풀이를 사용
for (int x = 0; x <= ages.length; x++){
    ages2[x] = ages[x]+1;
}

튜플

튜플tuple은 목록과 상당히 비슷하지만, 그것들은 불변이다. 일단 튜플이 정의되면, 그것은 바꿀 수가 없다. 목록과 마찬가지로 인덱스를 가지기는 하지만, 다시 말하거니와, 한번 정의하면 변경될 수가 없다. 따라서, 튜플에 있는 인덱스는 특정한 값을 추출하기 위해 사용하는 것이지 할당이나 수정을 위한 것이 아니다. 튜플이 목록과 비슷하게 보일지라도, 튜플은 보통 이질적인 원소들을 포함한다는 점에서, 연관성 있는 방식으로 원소를 저장하는 목록에 비해 상당히 다르다. 예를 들어, 튜플의 일반적인 용례는 함수, 메소드 등에 매개변수를 전달하는 것이다.

튜플은 순서형에 속하기 때문에, 모든 순서형의 연산에 쓸 수 있는 몇몇 동일한 메소드를 사용할 수 있다.

예제 2-18. 튜플 예제

>>> # 빈 튜플 생성
>>> myTuple = ()

>>> # 튜플 생성하고 사용하기
>>> myTuple2 = (1, 'two',3, 'four')
>>> myTuple2
(1, 'two', 3, 'four')

>>> # 마지막에 쉼표를 써서 한 항목짜리 튜플을 생성하기
>>> myteam = 'Bears',
>>> myteam
('Bears',)

앞서 언급했듯이, 튜플은 함수, 메소드, 클래스 등에 전달하기에 상당히 유용하다. 때때로 다중 값을 전달하는 데는 불변 개체를 갖는 것이 훌륭하다. 그러한 하나의 예는 지리 정보 시스템의 좌표를 전달하거나 그러한 종류의 다른 애플리케이션에 튜플을 사용하는 것이 될 수 있다. 불변의 개체가 보증되는 상황에서는 또한 훌륭하다. 그것들은 불변이기 때문에, 한번 정의되면 크기가 증가하지 않으며, 튜플은 메모리 할당이 관심사인 곳에서 중요한 역할을 수행할 수 있다.

사전

파이썬의 사전dictionary은 열쇠-값을 담는 그릇container이다. 사전은 주어진 원소에 대해 자동으로 채워지는 인덱스가 없으므로 파이썬의 전형적인 목록과는 상당히 다르다. 목록을 사용할 때는 어떠한 값에 대해 인덱스를 할당하는 것을 걱정할 필요가 없다. 사전은 개발자로 하여금 구조 내에 위치한 모든 원소에 대해 인덱스나 “열쇠”를 할당하도록 해준다. 그러므로, 사전에 들어가는 것은 열쇠와 원소를라는 두 값을 필요로 한다

사전이 아름다운 점은 개발자로 하여금 열쇠 값의 자료형을 선택할 수 있도록 해준다는 것이다. 따라서, 문자열 또는 그외의 정수나 부동소수점수 등의 해쉬 가능한 개체를 열쇠로 사용하는 것이 얼마든지 가능하다. 사전은 또한 사용하기에 좋도록 다수의 메소드와 연산을 가지고 있다. 표 2-5는 사전의 메소드와 함수를 나열한다.

예제 2-19. 기본적인 사전의 예

>>> # 빈 사전 및 채워진 사전을 생성
>>> myDict={}
>>> myDict.values()
[]

>>> # 사전에 열쇠-값 쌍을 할당
>>> myDict['one'] = 'first'
>>> myDict['two'] = 'second'
>>> myDict
{'two': 'second', 'one': 'first'}

표 2-5. 사전 메소드와 함수

메소드 또는 함수 설명
len(dictionary) 주어진 사전 내에서 항목의 수를 반환하는 함수
dictionary [key] 주어진 key와 관련된 사전의 항목을 반환
dictionary[key] = value 사전에서 연관된 항목에 주어진 vaule를 설정
del dictionary[key] 사전에서 주어진 key/value 쌍을 삭제
dictionary.clear() 사전에서 모든 항목을 제거하는 메소드
dictionary.copy() 사전의 얕은 복사본을 만드는 메소드
has_key(key) 사전이 주어진 key를 포함하는지의 여부를 반환하는 함수 (deprecate되었으며 in을 사용하는 것을 권장)
key in d 주어진 key를 사전에서 찾을 수 있는지의 여부를 반환
key not in d 주어진 key를 사전에서 찾을 수 없는지의 여부를 반환
items() 사전에서 key/value 쌍의 사본을 포함하는 튜플의 목록을 반환
keys() 사전 내에서 key들의 목록을 반환
update([dictionary2]) 주어진 사전으로부터의 키/값 쌍으로 사전을 갱신하며, 기존 키를 덮어 씀
fromkeys(sequence[,value]) 주어진 순서형로부터 키들을 가지고 새로운 사전을 생성하며 값은 주어진 값으로 설정됨
values() 사전의 값들을 목록으로 반환
get(key[, b]) 주어진 key에 연관된 값을 반환. key가 존재하지 않으면 b를 반환
setdefault(key[, b]) 주어진 key에 연관된 값을 반환. key가 존재하지 않으면, key 값의 b에 설정됨(mydict[key] = b)
pop(key[, b]) 주어진 key에 연관된 key/value 쌍을 반환하고 제거. key가 존재하지 않는 경우 b를 반환
popItem() 임의의 key/value 쌍을 사전으로부터 pop
iteritems() 사전에 있는 key/value 쌍에 대하여 반복자를 반환
iterkeys() 사전의 key들에 대하여 반복자를 반환
itervalues() 사전의 value들에 대하여 반복자를 반환

이제 우리는 몇 가지 사전 예제를 살펴볼 것이다. 이 참고자료는 각 사전 메소드와 함수의 사용법을 보여주지는 않을 것이지만, 그것들이 어떻게 동작하는지에 대해 충분한 기본 지식을 제공한다.

예제 2-20. 파이썬 사전 사용하기

>>> # 빈 사전을 생성하고 사전을 채움
>>> mydict = {}

>>> # 사전에서 열쇠를 찾아봄
>>> 'firstkey' in mydict
False

>>> # 사전에 열쇠/값 쌍을 추가
>>> mydict['firstkey'] = 'firstval'
>>> 'firstkey' in mydict
True

>>> # 사전의 값들을 나열
>>> mydict.values()
['firstval']

>>> # 사전의 열쇠들을 나열
>>> mydict.keys()
['firstkey']

>>> # 사전의 길이(열쇠/값 쌍이 얼마나 들어있는지)를 표시
>>> len(mydict)
1

>>> # 사전의 내용을 출력
>>> mydict
{'firstkey': 'firstval'}

>>> # 원본 사전을 문자열 기반 열쇠를 가진 사전으로 대체
>>> # 다음의 사전은 하키 팀을 표현
>>> myDict = {'r_wing':'Josh','l_wing':'Frank','center':'Jim','l_defense':'Leo','r_defense':'Vic'}
>>> myDict.values()
['Josh', 'Vic', 'Jim', 'Frank', 'Leo']
>>> myDict.get('r_wing')
'Josh'
>>> myDict['r_wing']
'Josh'

>>> # 존재하지 않는 열쇠에 대한 값을 얻기 위해 시도
>>> myDict['goalie']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'goalie'

>>> # get()을 사용하여 존재하지 않는 열쇠에 대한 값을 얻기 시도
>>> myDict.get('goalie')
>>>

>>> # 열쇠가 존재하지 않는 경우 기본 메시지를 표시
>>> myDict.get('goalie','Invalid Position')
'Invalid Position'

>>> # 사전의 항목들에 대하여 반복
>>> for player in myDict.iteritems():
...     print player
...
('r_wing', 'Josh')
('r_defense', 'Vic')
('center', 'Jim')
('l_wing', 'Frank')
('l_defense', 'Leo')

>>> # 열쇠와 값을 독립적인 개체에 할당한 다음 출력
>>> for key,value in myDict.iteritems():
...     print key, value
...
r_wing Josh
r_defense Vic
center Jim
l_wing Frank
l_defense Leo

집합

집합set은 정렬되지 않은, 고유한 원소의 모임이다. 집합이 다른 순서형 다른 점은 인덱스를 사용하지 않으며, 원소의 중복이 없다는 것이다. 원소에 연관된 열쇠값도 갖지 않으므로 사전과도 다르다. 집합은 고유한 요소의 임의의 모임이다. 집합은 변경가능한 개체를 포함할 수 없지만, 그 자체로서는 변경가능하다. 또 한 가지 유의할 점으로, 집합은 Sets 모듈로부터 수입하여야 하며, 그렇지 않으면 사용할 수 없다.

예제 2-21. 집합의 예

>>> # 집합을 사용하기 위해서는 우선 모듈을 들여와야한다.
>>> from sets import Set

>>> # 다음의 구문을 써서 집합을 생성한다
>>> myset = Set([1,2,3,4,5])
>>> myset
Set([5, 3, 2, 1, 4])

>>> # 집합에 값을 추가. 자세한 내용은 표 2-7을 참조
>>> myset.add(6)
>>> myset
Set([6, 5, 3, 2, 1, 4])

>>> # 중복되는 원소의 추가를 시도
>>> myset.add(4)
>>> myset
Set([6, 5, 3, 2, 1, 4])

집합의 두 가지의 종류로서, 집합set과 동결집합frozenset이 있다 둘 사이의 차이점은 이름에서 쉽게 읽힌다. 일반적인 집합은 변경가능한 개체의 모음인 반면, 동결집합은 불변이다. 불변 개체는 한번 만들어지면 변경이 되지 않는 반면, 가변 개체는 생성 후에 변경이 가능함을 기억하라. 순서형sequences 및 대응형mapping types과 마찬가지로, 집합에도 메소드와 연산이 갖추어져 있다. 대부분의 연산과 메소드는 가변 및 불변 집합 양쪽에 대하여 사용할 수 있다. 하지만, 일부 불변 집합에만 사용 가능한 것들도 있다. 표 2-6과 2-7에서, 다른 메소드와 연산을 살펴볼 것이다.

표 2-6. 집합 메소드와 함수

메소드 또는 연산 설명
len(set) 주어진 집합의 원소의 개수 반환
copy() 집합의 새로운 얕은 사본을 반환
difference(set2) 호출하는 집합에는 있지만 set2에는 없는 모든 원소의 새로 운 집합(차집합)을 반환
intersection(set2) 호출하는 집합과 set2에 공통되는 모든 원소로 이루어진 새 로운 집합(교집합)을 반환
issubbset(set2) 호출하는 집합의 모든 원소가 set2의 원소인지(부분집합)의 여부를 반환
issuperset(set2) set2의 모든 원소가 호출하는 집합에 포함되는지의 여부를 반환
symmetric_difference(set2) 호출하는 집합 또는 set2에 포함되지만 양쪽 모두에는 들어 있지 않은 새로운 집합(대칭차집합: set1 ^ set2)을 반환
x in set x가 set에 포함되는지의 시험하여 그 여부를 반환
x not in set x가 set에 포함되지 않는지를 시험하여 그 여부를 반환
union(set2) 호출하는 집합과 set2 양쪽에 속하는 원소로 이루어진 새로 운 집합(합집합)을 반환

예제 2-22. 집합 메소드와 함수 사용하기

>>> # 두 개의 집합을 생성
>>> s1 = Set(['jython','cpython','ironpython'])
>>> s2 = Set(['jython','ironpython','pypy'])

>>> # 집합의 사본을 만듦
>>> s3 = s1.copy()
>>> s3
Set(['cpython', 'jython', 'ironpython'])

>>> # s1에는 속하지만 s2에는 속하지 않는 모든 원소를 포함하는 새로운 집합을 구함
>>> s1.difference(s2)
Set(['cpython'])

>>> # 각 집합의 모든 원소를 포함하는 새로운 집합을 구함
>>> s1.union(s2)
Set(['cpython', 'pypy', 'jython', 'ironpython'])

>>> # 각 집합에 속하되 양쪽 모두에는 속하지 않는 원소로 이루어진 새로운 집합을 구함
>>> s1.symmetric_difference(s2)
Set(['cpython', 'pypy'])

표 2-7. 변경가능 집합의 메소드

메소드 또는 연산 설명
add(item) 집합에 속하지 않는 경우에 한하여 item을 집합에 추가
clear() 집합의 모든 항목을 제거
difference_update(set2) set2에 포함되는 원소를 모두 제거
discard(element) 지정된 element가 존재할 경우 집합에서 제거
intersection_update(set2) set2에도 포함되는 원소만 남김
pop() 집합에서 임의의 원소를 반환
remove(element) element가 집합에 남아있는 경우 반환하고, 그렇지 않으면 KeyError를 제기
symmetric_difference_update(set2) 호출되는 집합 또는 set2의 어느 한 쪽에 속하지만 양쪽 모두에 속하지는 않는 원소를 담는 집합으로 원래의 집합을 치환하여 반환
update(set2) 집합이 set2의 모든 원소를 포함하도록 하여 반환

예제 2-23. 집합의 다른 용례

>>> # 세 개의 집합을 생성
>>> s1 = Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
>>> s2 = Set([5, 10, 15, 20])
>>> s3 = Set([2, 4, 6, 8, 10])

>>> # s2로부터 임의의 원소를 제거
>>> s2.pop()
20
>>> s2
Set([5, 15, 10])

>>> # s1에 3과 동일한 원소가 존재하는 경우 폐기
>>> s1.discard(3)
>>> s1
Set([6, 5, 7, 8, 2, 9, 10, 1, 4])

>>> # s1과 s2 양쪽 모두에 속하는 원소들만을 가지도록 s1을 갱신
>>> s1.intersection_update(s2)
>>> s1
Set([5, 10])
>>> s2
Set([5, 15, 10])

>>> # s2의 모든 원소를 제거
>>> s2.clear()
>>> s2
Set([])

>>> # 집합 s3의 모든 원소를 포함하도록 s1을 갱신
>>> s1.update(s3)
>>> s1
Set([6, 5, 8, 2, 10, 4])

range

range는 숫자 범위에 대하여 반복을 하거나 특정한 범위의 숫자를 나열하도록 해주는 특별한 함수이다. 그것은 수학적인 반복에 특히 유용하지만, 간단한 반복을 위해서도 사용할 수 있다.

range 함수를 사용하는 형식은 선택사항인 시작값, 종료값, 그리고 선택사항인 증감분이다. 시작값을 지정한 경우 그것은 범위가 어디에서부터 시작하는지를 알려주며, 종료값은 범위를 어디에서 마쳐야하는지 표시한다. 시작값은 범위에 포함되며, 끝 숫자는 포함되지 않는다. 선택사항인 증감분은 출력 범위에 포함시킬 숫자 사이에 몇 개의 숫자가 위치하는지를 알려준다. 증감분은 이전 숫자에 더해지며 만약 그 숫자가 종료값을 초과하면 range는 중단된다.

range 형식

range([시작값], 종료값, [증감분])

예제 2-24. range 함수 사용하기

>>> # 0으로 시작하는 간단한 범위. 끝점이 범위에 포함되지 않음에 유의할 것
>>> range(0,10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(50, 65)
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64]
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> # 단계수 2를 지정
>>> range(0,10,2)
[0, 2, 4, 6, 8]

>>> # 단계를 음수로 지정할 경우 동일한 기능이 수행됨... 단계는 이전 숫자에 더해짐
>>> range(100,0,-10)
[100, 90, 80, 70, 60, 50, 40, 30, 20, 10]

이 함수가 가장 보편적으로 사용되는 곳은 for 되풀이이다. 다음의 예는 for 되풀이를 위해 range 함수를 사용하는 두 가지 방법을 보여준다.

예제 2-25. for 되풀이에서 range 함수 사용하기

>>> for i in range(10):
...     print i
...
0
1
2
3
4
5
6
7
8
9

>>> # 곱셈의 예
>>> x = 1
>>> for i in range(2, 10, 2):
...     x = x + (i * x)
...     print x
...
3
15
105
945

보는 바와 같이, range는 임의의 숫자 집합에 대하여 반복하는 데에 사용할 수 있다. 증가하든 감소하든, 단계수가 양수이든 음수이든. range는 숫자 목록을 생성하는 좋은 방법이기도 하다. 숫자 목록을 생성하려면, 다음과 같이 목록을 list()로 넘기기만 하면 된다.

예제 2-26. range로부터 목록을 생성

>>> my_number_list = list(range(10))
>>> my_number_list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

보는 바와 같이, range는 반복에 유용할 뿐만 아니라 숫자 목록을 생성하는 좋은 방법이기도 하다.

자이썬 전용 모임

우리가 사용할 수 있는 많은 수의 자이썬 전용 모임Collection 개체가 있다. 이러한 모임 개체들은 자바 클래스로 자료를 전달하는 데 사용되지만, 자이썬 구현에는 파이썬을 처음 접하는 자바 개발자를 위한 부가적인 기능도 넣어두었다. 그럼에도 불구하고, 이러한 부가적인 모임 개체들 다수는 특정한 상황에서 제법 쓸 만하다.

자이썬 2.2 판에 들어서 자바 컬렉션 통합이 등장했다. 이것은 자이썬과 자바 컬렉션 유형 간의 양방향의 상호작용을 가능하게 한다. 예를 들면, 자바의 ArrayList를 자이썬으로 가져와서 마치 언어의 일부인 양 사용할 수 있다. 2.2 이전에서는, 자바 컬렉션 개체는 자이썬 개체처럼 쓸 수 있지만, 자이썬 개체는 자바 개체처럼 쓸 수 없었다. 예를 들어, 자바의 ArrayList를 자이썬에서 사용하는 것이 가능하며 add(), remove(), 그리고 get()과 같은 메소드를 사용할 수 있다. 아래의 예제에서 ArrayList의 add() 메소드를 사용하여 목록에 항목을 추가하고 그 결과가 성공하였는지 실패하였는지를 의미하는 부울값을 반환하는 것을 볼 수 있다. remove() 메소드도 그와 비슷하게 동작하는데, 항목을 추가하는 대신 제거하는 일을 한다.

예제 2-27. 자바의 모임을 자이썬에서 사용하는 예제

>>> # 자바의 ArrayList를 들여와서 사용
>>> import java.util.ArrayList as ArrayList
>>> arr = ArrayList()

>>> # Add 메소드는 목록에 항목을 추가하고 그에 대한 성공 여부를 반환
>>> arr.add(1)
True
>>> arr.add(2)
True
>>> print arr
[1, 2]

자바 모임의 통합은 물론, 자이썬에서 자바 배열을 만들 수 있도록 해주는 jarray 개체 역시 구현되어 있다. jarray를 사용하기 위해서는, 단순히 순서형 자료를 정의하면 된다. jarray는 자바 배열을 만들어서 자바 객체에 전달하는 데 있어서 대단히 유용하지만, 자이썬 개체에 대해서 작업하기에는 그다지 유용하지 못하다. 그 뿐 아니라, jarray 내의 모든 값은 동일한 유형이어야 한다. 여러 유형을 담고 있는 순서형 자료를 jarray로 넘기려하면 한두 가지 TypeError가 발생할 것이다. 표 2-8의 jarray에서 쓰이는 자료형 코드 문자 목록을 보라.

표 2-8. jarray에서 사용하는 자료형 코드 문자

문자 자바 자료형
z boolean
b byte
c char
d Double
f Float
h Short
i Int
l Long

예제 2-28. jarray 사용

>>> my_seq = (1,2,3,4,5)
>>> from jarray import array
>>> array(my_seq,'i')
array('i', [1, 2, 3, 4, 5])
>>> myStr = "Hello Jython"
>>> array(myStr,'c')
array('c', 'Hello Jython')

또 다른 유용한 jarray의 기능은, 원할 때는 zeros() 메소드를 써서 빈 배열을 생성할 수 있다는 것이다. zeros() 메소드는 앞서 살펴본 array() 메소드와 비슷한 방식으로 동작한다. 비어 있는 배열을 생성하기 위해서는, 그저 배열의 길이를 유형과 함께 zeros() 메소드에 넘기면 된다. 예제를 잠깐 살펴보자.

예제 2-29. 빈 부울 배열 생성

>>> arr = zeros(10,'z')
>>> arr
array('z', [False, False, False, False, False, False, False, False, False, False])

예제 2-30. 빈 정수 배열 생성

>>> arr2 = zeros(6, 'i')
>>> arr2
array('i', [0, 0, 0, 0, 0, 0])

자바 개체로 작업하는 몇몇 상황에서는, 자바 배열을 인자로 하는 자바 메소드를 호출할 필요가 있을 것이다. 필요할 때 jarray 개체를 사용하면 자바 배열을 간단히 생성할 수 있다.

파일

파일 개체는 디스크에 있는 파일을 읽거나 쓰는 데 사용한다. 파일 개체는 디스크의 파일에 대한 참조를 얻는 데 사용하며, 그것을 열어서 읽고, 쓰고, 추가하는 등의 여러 작업을 하는 데 쓰인다. 단순히 open(파일명[, 모드]) 함수를 써서, 파일 개체를 반환하며 처리를 위하여 변수에 할당할 수 있다. 파일이 아직 디스크에 존재하지 않으면, 그때 자동으로 생성된다. 모드 인자는 파일에 대하여 어떤 종류의 처리를 하고자 하는지 알려주는 데 쓰인다. 이 인자는 선택사항이며 생략할 경우에는 파일을 읽기 전용 모드로 열게 된다. 표 2-9 참조.

표 2-9. 파일 유형에 대한 작업의 구분

모드 설명
‘r’ 읽기 전용
‘w’ 쓰기(주의: 파일에 다른 것이 있다 하더라도 경고 없이 덮어 씀)
‘a’ 추가
‘r+’ 읽기 및 쓰기
‘rb’ 이진(binary) 파일 읽기
‘wb’ 이진 파일 쓰기
‘r+b’ 이진 파일 읽기 및 쓰기

예제 2-31.

>>> # 파일을 열고 변수 f에 할당
>>> f = open('newfile.txt','w')

파일 내용을 조작하기 위하여 파일 개체에 대하여 사용할 수 있는 수많은 메소드가 있다. 파일 내용을 읽기 위해 파일에 대하여 read([크기])를 호출할 수 있다. 크기는 선택적 인수로서 파일로부터 얼마 만큼의 내용을 읽을지 알려주는 데 쓰인다. 생략할 경우에는 파일 전체 내용을 읽는다. readline() 메소드는 파일로부터 한 줄을 읽는 데 쓰인다. readlines([크기])는 파일 내에 포함된 모든 행을 포함하는 목록을 반환하는 데 쓰인다. 다시 말하지만, 크기 인자는 파일로부터 몇 바이트를 읽을 것인지 알려주기 위해 사용할 수 있는 선택적 인자인다. 파일에 내용을 넣고 싶으면 write(문자열) 메소드를 쓰면 된다. write() 메소드는 파일에 문자열을 기록한다.

파일에 쓸 때는 어느 위치에 쓰는지 정확하게 아는 것이 중요할 때가 종종 있다. 숫자를 써서 파일에서의 바이트를 나타냄으로써 위치를 구하는 데 도움이 되는 메소드들이 있다. 파일 개체의 현재 위치를 얻기 위해 파일의 tell() 메소드를 호출할 수 있다. 파일의 시작으로부터 몇 바이트 떨어져 있는지를 나타내는 정수를 반환한다. seek(offset, from) 메소드는 파일에서 위치를 변경하는 데 쓸 수 있다. offset은 가고 싶은 곳의 위치를 바이트로 나타낸 수이며, from은 어디서부터 오프셋을 계산할지를 나타낸다. from이 0이면, offset은 파일의 시작에서부터 계산한다. 마찬가지로, from이 1이라면현재 파일 위치에서 계산하며, 2는 파일의 끝에서부터가 될 것이다. from을 생략할 경우 기본값은 0이다.

끝으로, 프로그램에서 자원의 할당과 반납을 효과적으로 처리함으로써 메모리의 부담을 줄이고 새어나가는 것도 막을 수 있다. CPython과 자이썬에서 자원을 관리하는 방법이 좀 다른데, 그것은 쓰레기 수거의 동작이 서로 다르기 때문이다. CPython에서는 자원이 유효범위scope에서 벗어나면 자동적으로 반납되므로 자원 반납에 대하여 그리 걱정하지 않아도 된다. JVM은 즉시 쓰레기 수거를 기록하므로 적절한 자원 반납이 더욱 중요하다. 파일 작업을 마친 후에는 그에 대하여 close() 메소드 호출해야만 한다. 파일에 대하여 작업할 때에 적절한 방법론은 매번 열고, 처리하고, 닫는 것이다. 그러한 일을 보다 호율적으로 처리하는 방법이 있기는 하다. 7장에서 그러한 일을 보다 효율적으로 처리해주는 문맥 관리자의 사용에 대하여 다룰 것이다.

예제 2-32. 파이썬으로 파일 조작

>>> # 파일을 생성하고, 내용을 기록한 다음 읽음
>>> f = open('newfile.txt','r+')
>>> f.write('This is some new text for our file\n')
>>> f.write('This should be another line in our file\n')

>>> #  기록된 내용의 끝에 와 있으므로 아무 행도 읽지 못함
>>> f.read()
''
>>> f.readlines()
[]
>>> f.tell()
75L

>>> # 파일의 시작 부분으로 위치 이동
>>> f.seek(0)
>>> f.read()
'This is some new text for our file\nThis should be another line in our file\n'
>>> f.seek(0)
>>> f.readlines()
['This is some new text for our file\n', 'This should be another line in our file\n']

>>> # 파일을 닫고 반납
>>> f.close()

반복자

반복자iterator는 파이썬 버전 2.2에서 도입되었다. 반복자는 파이썬 컨테이너에 대하여 반복을 하도록 해준다. 반복가능한 컨테이너는 모두 반복자 유형에 대한 지원을 내장하고 있다. 예를 들어, 순서형 개체들은 각 원소에 대하여 반복을 허용하므로 반복 가능한 개체이다. 반복을 지원하지 않는 개체에 대하여 반복자를 반환하려고 하면, 그 개체에 대한 속성으로서 __iter__가 정의되지 않았음을 의미하는 AttributeError를 얻게 될 것이다. 메소드 이름의 앞뒤에 밑줄 두 개씩을 붙인 것은 파이썬에서 특별한 메소드이다. 예를 들어 파이썬 클래스는 __init__() 메소드를 사용하여 초기화할 수 있다. 자바에서의 생성자와 비슷하다. 클래스에 대한 상세한 내용과 특별한 클래스 메소드에 대해서는 7장을 참조하기 바란다.

반복자는 순서형이라든지 기타 반복 가능한 컨테이너들에 손쉽게 접근하도록 해준다. 사전과 같이 특수한 반복 메소드를 가지는 컨테이너도 있음을 앞에서 살펴보았다. 반복자 개체는 반복을 위해 규정된 양식에 맞는 두 가지 주요한 메소드를 지원하여야 한다. 표 2-10에 그러한 메소드를 기술하였다.컨테이너에 대하여 반복자를 반환하기 위해서는 그저 container.__iter__()를 변수에 할당하면 된다. 그 변수가 그 개체에 대한 반복자가 될 것이다.이렇게 함으로써 반복자를 함수 같은 것들에 전달할 수 있다. 그러면 그 반복자는 스스로의 상태를 나타내는 변수를 변경하는 것처럼 될 것이다. 원본 개체에 영향을 주지 않고 반복자를 사용할 수 있다. next()를 호출하는 경우, 모든 항목을 찾을 때까지 목록의 다음번 항목을 반환하는 일을 계속한다. 이런 일이 일어나면, StopIteration 예외가 발생한다. 반복자를 반환하여 그것을 변수에 할당할 때에는 실제로 목록의 사본을 생성하게 된다는 것이 중요하다. 그 변수는 next() 메소드가 호출될 때마다 그 사본으로부터 항목을 반환하면서 제거한다. 반복자에 대하여 StopIteration 오류가 발생할 때까지 계속 next()를 불러댄다면, 그 변수는 더 이상 어떠한 항목도 담고 있지 않아서 비게 된다. 다음의 예를 보면, 목록으로부터 반복자를 생성한 다음 모든 값을 다 찾을 때까지 next() 메소드를 호출하면 그 반복자는 텅 비게 되지만, 원본은 그대로 남아있게 된다.

예제 2-33. 목록으로부터 반복자를 생성하여 사용하기

>>> hockey_roster = ['Josh', 'Leo', 'Frank', 'Jim', 'Vic']
>>> hockey_itr = hockey_roster.__iter__()
>>> hockey_itr.next()
'Josh'
>>> for x in hockey_itr:
...     print x
...
Leo
Frank
Jim
Vic

>>> # 반복자의 모든 원소를 다 써버린 다음에 next()를 호출 시도
>>> hockey_itr.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

예제 2-34. 순서형 및 목록에 대하여 반복

>>> # 문자열과 목록에 대하여 반복
>>> str_a = 'Hello'
>>> list_b = ['Hello','World']
>>> for x in str_a:
...     print x
...
H
e
l
l
o
>>> for y in list_b:
...     print y + '!'
...
Hello!
World!

참조와 복사

파이썬에서 사본을 만들고 항목을 참조하는 것은 단순명료하다. 명심해야할 단 한 가지는, 변경 가능한 개체를 복사하는 것과 변경 불가능한 개체를 복사하는 것은 방법에 차이가 있다는 점이다.

변경 불가능한 개체에 대한 사본을 생성하기 위해서는, 단순히 다른 변수에 할당하기만 하면 된다. 새로운 변수는 곧 그 개체의 사본이다. 변경가능한 개체에 대해서 똑같은 일을 하게 되면, 실제로는 원본 개체에 대한 참조를 생성하는 것이 된다. 그러므로 “사본”에 대한 연산을 수행한다고 해도 실제로는 원본에 영향을 미치게 된다. 이런 문제가 생기는 이유는 새로 할당된 것이 기억장소 내의 원본과 동일한 변경가능 개체를 참조하기 때문이다. 이것은 마치 누군가가 당신을 다른 이름으로 부르는 것과 같다. 어떤 사람은 당신을 본명으로 부르고, 어떤 사람은 별명으로 부를 수도 있지만, 사실상 두 이름은 동일한 사람을 지칭하는 것이다.

예제 2-35. 사본을 가지고 작업하기

>>> # 문자열은 불변이므로 문자열을 다른 변수에 할당하면 진짜 사본을 생성한다.
>>> mystring = "I am a string, and I am an immutable object"
>>> my_copy = mystring
>>> my_copy
'I am a string, and I am an immutable object'
>>> mystring
'I am a string, and I am an immutable object'
>>> my_copy = "Changing the copy of mystring"
>>> my_copy
'Changing the copy of mystring'
>>> mystring
'I am a string, and I am an immutable object'

>>> # 목록은 변경가능 개체이므로, 목록을 변수에 할당하면 그 목록에 대한 참조를 생성한다.
>>> # 이 변수들 중 하나를 변경하면 다른 것도 바뀐다. 그것들은 동일한 개체에 대한 참조인 것이다.
>>> listA = [1,2,3,4,5,6]
>>> print listA
[1, 2, 3, 4, 5, 6]
>>> listB = listA
>>> print listB
[1, 2, 3, 4, 5, 6]
>>> del listB[2]

>>> # 이런, 원본 목록까지 변경해버렸다!
>>> print listA
[1, 2, 4, 5, 6]

>>> # 원본을 참조하는 것이 아니라, 동일한 원소로 이루어진 새로운 목록을 생성할 때에는 copy 모듈을 사용한다.
>>> import copy
>>> a = [[]]
>>> b = copy.copy(a)
>>> b
[[]]

>>> # b는 a와 동일한 목록이 아닌, 사본임
>>> b is a
False

>>> # 하지만 목록 b[0]은 a[0]과 동일한 목록이며, 한쪽을 변경하면 다른 쪽도 변경된다.
>>> # 이것을 얕은 복사라고 한다. 높은 수준에서는 a와 b가 서로 다르지만, 한 단계 내려가 보면 동일한 것을 가리키고 있다.
>>> # 깊이 들어가보면 사본이 아니라 동일한 개체인 것이다.
>>> b[0].append('test')
>>> a
[['test']]
>>> b
[['test']]

변경가능한 개체의 사본을 효과적으로 생성하는 방법에는 두 가지가 있다. 원본 개체에 대한 얕은 복사본을 생성하거나 깊은 복사본을 생성하는 것이다. 차이점은, 개체를 얕게 복사하게 되면 새로운 개체를 만들어서는 원본 개체에 들어있는 것으로 속을 채운다는 것이다. 즉, 동일한 항목에 대해 참조를 하고 있으므로, 만약 어느 한 쪽이라도 변경을 가하면 양쪽에 영향을 미치게 된다.

깊은 복사는 새로운 개체를 만들고 원본 개체의 내용을 일일이 복사해서 새로 만든 사본에 넣는다. 깊은 복사를 행한 경우에는 다른 개체에는 영향을 주지 않고 어느 한 쪽에 대해서만 연산을 할 수 있다. 파이썬 표준 라이브러리의 deepcopy 함수를 사용하여 그러한 사본을 생성할 수 있다. 이것이 어떻게 동작하는지에 대한 이해를 돕기 위해 예제를 좀 더 살펴보도록 하겠다.

예제 2-36.

>>> # 정수값을 생성하고, 복사한 다음, 수정
>>> a = 5
>>> b = a
>>> print b
5
>>> b = a * 5
>>> b
25
>>> a
5

>>> # 목록의 깊은 복사본을 생성하여 수정
>>> import copy
>>> listA = [1,2,3,4,5,6]
>>> listB = copy.deepcopy(listA)
>>> print listB
[1, 2, 3, 4, 5, 6]
>>> del listB[2]
>>> print listB
[1, 2, 4, 5, 6]
>>> print listA
[1, 2, 3, 4, 5, 6]

쓰레기 수거

CPython과 자이썬의 주요한 차이점 중 하나가 쓰레기 수거garbage collection이다. CPython에서는 눈 밖에 나거나 더 이상 필요하지 않은 개체를 쓰레기로 간주하여 수거해간다. 이것은 자동으로 일어나는 일이라서 개발자가 신경 쓸 필요가 없다. 보이지 않는 곳에서, CPython은 참조 횟수 세기 기법을 통해 각각의 개체에 대한 숫자를 셈으로써 그 개체가 계속 사용중인지를 효과적으로 판별한다. CPython과는 달리 자이썬은 사용하지 않는 개체에 대한 쓰레기 수거를 처리하기 위한 참조 회수 세기 기법을 구현하지 않는다. 그 대신에, 자이썬은 자바 환경에서 제공하는 쓰레기 수집 기술을 그대로 사용한다. 자이썬 개체가 낡아버렸거나 접근할 수 없게 되었을 때, 그것을 회수할 지 그렇지 않을 지는 자바 가상 기계가 판단한다. 자바 가상 기계가 개발자를 행복하게 만들어주는 점 가운데 하나가 코드 작성 후에 더 이상 청소에 대한 걱정을 하지 않아도 된다는 것이었다. C 프로그래밍 언어에서는, 개발자는 어느 개체가 현재 사용중인지 항상 생각하고 있다가 그것들이 더 이상 필요하지 않을 때에는 청소를 해주어야 한다. 반면에 자바 세계에서는 자바 가상 기계의 gc 스레드가 쓰레기 수거와 청소를 알아서 해준다.

클래스에 대한 자세한 내용은 아직 다루지 않았지만, 1장에서 그에 대한 짧은 예제를 살펴본 적이 있다. 여기서 파이썬이 개체를 정리하는 원리를 살펴보는 것이 좋을 것 같다. 어느 클래스에서든 종결자finalizer 메소드를 정의하여 쓰레기 수집기가 특정한 작업을 수행하도록 만들 수 있다. 범위 밖으로 나가버린 개체가 있을 때 청소하는 코드를 이 종결자 메소드 안에 배치하면 된다. 하지만 개체가 오래되었다고 해도 종결자 메소드가 항상 호출될 것으로 기대할 수는 없다는 점을 명심하라. 종결자 메소드는 자바 쓰레기 수집기 메소드에서 호출하는 것이며, 쓰레기 수집기가 언제 호출될 것인지는 확실히 알 수 없기 때문이다. 또 다른 논점으로는 종결자가 성능 손실을 유발한다는 것이다. 그렇지 않아도 굼뜨는 애플리케이션을 만들어 놓았다면, 종결자를 남용하는 것은 좋지 않다.

다음은 파이썬 종결자의 예제이다. 종결자는 인스턴스 메소드이며 반드시 이름을 __del__이라고 지어야 한다.

예제 2-37. 파이썬 종결자 예제

class MyClass:
    def __del__(self):
        pass    # 여기에서 정리작업을 수행할 것

자바 가상 기계의 쓰레기 수거를 사용하는 것의 단점은 버려야 할 개체가 언제 정리될지 보증할 수 없다는 것이다. 따라서 성능이 중요시되는 개체에 대해서는 종결자에 기대지 않는 것이 상책이다. 파일이나 데이터베이스와 같은 개체를 가지고 작업을 하는 경우에는 항상 적절한 코딩 기법을 사용하는 것이 중요하다. 종결자가 호출되지 않는 경우도 있으므로, 파일을 닫는 close() 메소드를 종결자에 넣어서는 안된다. 필수적인 정리 작업에 대해서는 종결자가 호출되기 전에 모두 수행해버리는 것이 최선이다.

요약

이 장에서는 많은 내용을 다루었다. 읽어나가면서 파이썬에 대해 친숙해졌기를 바란다. 이 장의 도입부분에서는 자료를 특정한 개체나 자료형에 할당하는 방법을 다루었다. 자료형마다 차이가 있기 때문에, 각각의 자료형에 대해 작업할 때는 서로 다른 방식으로 풀어나가야 한다는 것을 배웠다. 자료형 탐험은 숫자와 문자열로부터 시작하였으며, 문자열 개체에 대해 사용할 수 있는 여러 가지 메소드에 대하여 논의하였다. 문자열은 목록이나 튜플 등의 파이썬 모임 개체와 마찬가지로 순서형의 한 부분임을 배웠다. 목록을 생성하는 법과, 그것을 사용하는 여러 가지 방법을 배웠다. 목록 함축을 통해 주어진 목록에 대한 사본을 만들며 그 원소들을 표현식이나 함수를 통해 조작할 수 있음을 배웠다. 목록 다음으로는 사전, 집합, 튜플을 논의하였다.

모임 형식을 논의한 다음, 자이썬은 파이썬에는 없는 자체적인 모임 개체도 가지고 있음을 배웠다. 자이썬에서는 자바 플랫폼의 장점을 손쉽게 활용하고 자바 컬렉션 형식을 사용할 수도 있다. 참조, 복사, 쓰레기 수거에 대한 논의로써 이 장을 마쳤다. 서로 다른 복사 방법은 때로는 예상과 다른 결과로 이어질 수 있으며, 자이썬은 파이썬과는 다른 방식으로 쓰레기를 수거한다.

다음 장에서는 표현식을 정의하고 통제 흐름을 가지고 작업하는 방법을 배울 것이므로, 이 장에서 배운 몇 가지 주제를 조합하는 데에 도움이 될 것이다.