일반인을 위한 파이썬 지침서/리스트를 더 자세히

우리는 이미 리스트가 어떻게 사용될 수 있는지 살펴본 바 있다. 이제 여러분은 좀더 배경지식을 같게 되었으므로 나는 리스트에 대하여 좀 더 자세하게 들어가 보려고 한다. 먼저 리스트에서 요소를 획득하는 더 많은 방법을 살펴볼 것이다. 그리고 우리는 그것들을 복사하는 것에 대하여 논의할 것이다.

여기에 지표를 사용하여 리스트의 구성요소에 접근하는 약간의 예제가 있다.

>>> list = ['zero','one','two','three','four','five']
>>> list[0]
'zero'
>>> list[4]
'four'
>>> list[5]
'five'

이러한 모든 예제들은 여러분에게 매우 친숙하게 보여야만 한다. 여러분이 리스트에서 첫 번째 항목을 원한다면 단지 지표 0을 살펴보라. 두 번째 항목은 지표 1 이고 그런식으로 리스트의 마지막까지 간다. 그렇지만 여러분이 리스트의 마지막 항목을 원한다면 어떻게 할까? 하나의 방법은 'list[len(list)-1]'와 같이 len 함수를 사용하는 것이다. len함수가 항상 마지막 지표 더하기 1 값을 반환해 주므로 이것은 잘 작동한다. 마지막으로부터 두 번째는 그러면 list[len(list)-2] 이 될 것이다. 이것을 위한 더 쉬운 방법이 있다. 파이썬에서 가장 마지막 항목은 항상 'index-1' 이다. 마지막에서 두번째는 'index -2' 이다. 등등. 여기에 예제를 더 보인다면 다음과 같다.

>>> list[len(list)-1]
'five'
>>> list[len(list)-2]
'four'
>>> list[-1]
'five'
>>> list[-2]
'four'
>>> list[-6]
'zero'

이렇게 리스트의 어떤 항목도 두 가지 방법으로 지표화 될 수 있다. 앞으로 부터 그리고 뒤로 부터

리스트의 부분으로 들어가는 또 다른 유용한 방법은 썰기이다. 여기에 여러분에게 썰기가 무엇에 유용한지 아이디어를 제공하는 또 다른 예제가 있다.

>>> list = [0,'Fred',2,'S.P.A.M.','Stocking',42,"Jack","Jill"]
>>> list[0]
0
>>> list[7]
'Jill'
>>> list[0:8]
[0, 'Fred', 2, 'S.P.A.M.', 'Stocking', 42, 'Jack', 'Jill']
>>> list[2:4]
[2, 'S.P.A.M.']
>>> list[4:7]
['Stocking', 42, 'Jack']
>>> list[1:5]
['Fred', 2, 'S.P.A.M.', 'Stocking']

썰기는 리스트의 부분을 반환하는데 사용된다. 썰기 연산자는 list[first_index:following_index]의 형태로 사용된다. 썰기는 first_index로부터 following_index앞의 지표까지 실행된다. 여러분은 지표화 형태 두 가지 모두를 사용할 수 있다.

>>> list[-4:-2]
['Stocking', 42]
>>> list[-4]
'Stocking'
>>> list[-4:6]
['Stocking', 42]

썰기에 대한 또 다른 꼼수는 지표를 생략하는 것이다. 만약 첫 번째 지표가 생략되면 리스트의 처음이라고 가정된다. 만약 다음의 지표가 생략되면 그 리스트의 나머지 전체라고 가정된다. 여기에 약간의 예제가 있다.

>>> list[:2]
[0, 'Fred']
>>> list[-2:]
['Jack', 'Jill']
>>> list[:3]
[0, 'Fred', 2]
>>> list[:-5]
[0, 'Fred', 2]

여기에 하나의 프로그램 예제가 있다.(여러분이 원한다면 시를 정의하는 형태로 복사하고 붙여넣어라.)

poem = ["<B>","Jack","and","Jill","</B>","went","up","the","hill","to","<B>",\
"fetch","a","pail","of","</B>","water.","Jack","fell","<B>","down","and",\
"broke","</B>","his","crown","and","<B>","Jill","came","</B>","tumbling",\
"after"]

def get_bolds(list):
        true = 1
        false = 0
        ## is_bold tells whether or not the we are currently looking at
        ## a bold section of text.
        is_bold = false
        ## start_block is the index of the start of either an unbolded
        ## segment of text or a bolded segment.
        start_block = 0
        for index in range(len(list)):
                ##Handle a starting of bold text
                if list[index] == "<B>":
                        if is_bold:
                                print "Error:  Extra Bold"
                        ##print "Not Bold:",list[start_block:index]
                        is_bold = true
                        start_block = index+1
                ##Handle end of bold text
                if list[index] == "</B>":
                        if not is_bold:
                                print "Error: Extra Close Bold"
                        print "Bold [",start_block,":",index,"] ",\
                        list[start_block:index]
                        is_bold = false
                        start_block = index+1

get_bolds(poem)

출력을 보인다면 다음과 같다.

Bold [ 1 : 4 ]  ['Jack', 'and', 'Jill']
Bold [ 11 : 15 ]  ['fetch', 'a', 'pail', 'of']
Bold [ 20 : 23 ]  ['down', 'and', 'broke']
Bold [ 28 : 30 ]  ['Jill', 'came']

get_bold함수는 하나의 리스트를 불러들여 단어와 토큰으로 분리한다. 그 함수가 찾는 토큰은 굵은 글씨체의 시작을 나타내는 <B>와 굵은 글씨체의 마지막을 나타내는 ' <\B>' 이다. 그 함수 get_bold는 진행하면서 처음과 마지막 토큰을 탐색한다.

리스트의 다음 사양은 그들을 복사하는 것이다. 여러분이 다음과 같이 간단한 어떤 것을 시도해 본다면 다음과 같다.

>>> a = [1,2,3]
>>> b = a
>>> print b
[1, 2, 3]
>>> b[1] = 10
>>> print b
[1, 10, 3]
>>> print a
[1, 10, 3]

이것은 아마도 놀라워 보일 것이다. 왜냐하면 b 에 대한 변경이 a 를 변경한 결과가 되었기 때문이다. 상황은 'b = a' 서술문이 b 를 a 에 대한 참조점으로 만들었기 때문이다. 이것이 뜻하는 바는 b를 a의 다른 이름으로 생각해도 좋다는 것을 의미한다. 그러므로 b에 대한 어떠한 변경도 a 를 역시 변경시킨다. 그렇지만 b 전체에다 할당하는 함수를 사용하면서 a 를 변경하지 마라.

>>> a = [1,2,3]
>>> b = a
>>> b = b*2
>>> print a
[1, 2, 3]
>>> print b
[1, 2, 3, 1, 2, 3]

이 경우에는 'b = b*2'서술문이 복사본 하나를 생성하므로 b 는 더 이상 a에 대한 참조점이 아니다. 기본적으로 여러분이 'whole_list_b = whole_list_a'와 같은 서술문을 가지고 있다면 여러분은 하나의 참조를 생성하고 있는 것이다. 여러분이 리스트를 인수로서 함수에 넘길 때에도 여러분은 역시 주소점을 생성하고 있는 것이다. whole_list_b 에 대한 부차적인 변경은 원래의 리스트를 변경할 것이다. 대부분의 경우에 여러분은 이러한 불일치에 대하여 걱정할 필요가 없다. 그렇지만 여러분은 리스트의 복사본을 가질 필요가 있을 때 실제로 하나의 복사본을 생성했는지 확실하게 해야만 한다.

리스트를 복사하는 여러 방법들이 있다. 대부분의 경우에 있어서 잘 작동하는 가장 간단한 것은 썰기(slice) 연산자이다.

>>> a = [1,2,3]
>>> b = a[:]
>>> b[1] = 10
>>> print a
[1, 2, 3]
>>> print b
[1, 10, 3]

썰기 "[:]"는 리스트에 대한 복사본을 생성한다. 그렇지만 바깥쪽 리스트만 복사한다. 만약 그 리스트가 리스트를 포함하고 있다면 안 쪽의 리스트도 역시 복사될 필요가 있다. 여러분은 그것을 수동으로 할 수도 있게지만, 파이썬은 이미 그런 일을 하는 모둘을 포함하고 있다. 여러분은 copy 모듈에 있는 deepcopy 함수를 사용할 수 있다.

>>> import copy
>>> a = [[1,2,3],[4,5,6]]
>>> b = a[:]
>>> c = copy.deepcopy(a)
>>> b[0][1] = 10
>>> c[1][1] = 12
>>> print a
[[1, 10, 3], [4, 5, 6]]
>>> print b
[[1, 10, 3], [4, 5, 6]]
>>> print c
[[1, 2, 3], [4, 12, 6]]

무엇보다도 a 는 배열의 배열이라는 것을 주목하라. 안쪽의 배열은 썰기 연산자로 정확하게 복사되지 않는다. 그렇지만 deepcopy로 c는 정확하게 복사된다. 잠-깐 : 이런게 의미가 있는가?

지금까지 여러분은 아마도 왜 참조가 사용되는지 도대체 이해가 안갈 것이다? 기본적인 이유는 속력 때문이다. 수천의 요소들 모두를 복사하는 것 보다는 그것들을 참조하는 편이 훨씬 더 빠르다. 그러지 않아야 할때 데이타가 변하는 괴이한 문제를 여러분이 가지더라도 참조에 관하여 여러분은 단지 이 사실만은 기억하라.