티스토리 뷰

3. Strings, lists, and tuples

openbookproject의 Beginning Python Programming for Aspiring Web Developers 3장 번역  [Copyright Notice]

이전글에서 이어집니다.

3.7. 리스트는 변경 가능하다(mutable)

시퀀스의 세 가지 타입 중에서 스트링과 튜플은 값을 변경할 수 없다(immutable objects). 값을 변경할 수 없다는 의미는 객체가 일단 한번 만들어지면 그 객체의 요소의 값을 변경하거나 삭제, 추가할 수 없다는 의미이다. 만약 요소의 변경이 필요하다면 새로운 객체를 만들어야 한다.

그러나 리스트는 요소들을 변경할 수 있다. 가장 쉽게는 지정을 이용해서 요소의 값을 바꿀 수 있다. 인덱스 표현의 요소를 지정문의 왼쪽에 쓰면 해당 요소의 값을 바꿀 수 있다.

>>> fruit = ["banana", "apple", "quince"]
>>> fruit[0] = "pear"
>>> fruit[-1] = "orange"
>>> fruit ['pear', 'apple', 'orange']

리스트를 가리키는 변수 이름 뒤에 대괄호로 인덱싱을 한 것은 수식의 어디에서든 나타날 수 있다. 그 때는 해당 요소 값을 가져오는 역할을 한다. 지정문의 왼쪽에 나타나면 리스트에 있는 해당 요소를 변경시킨다. 위의 예에서 첫 요소가 'banana'에서 'pear'로 바뀌고 마지막 요소가 'quince'에서 'orange'로 바뀌었다. 위와 같은 한 요소의 지정은 스트링에서는 동작하지 않는다.

>>> my_string = 'TEST'
>>> my_string[2] = 'X'
Traceback (most recent call last): File "<stdin>", line 1, in <module> 
TypeError: 'str' object does not support item assignment

여기서 보듯이 스트링은 한 요소를 지정으로 값을 바꾸는 것이 허용되지 않는다. 그러나 리스트는 가능하다. 다음은 문자 네 개를 가진 리스트에서 한 요소의 값을 바꾼 것이다.

>>> my_list = ['T', 'E', 'S', 'T']
>>> my_list[2] = 'X'
>>> my_list ['T', 'E', 'X', 'T']

한 개 요소의 값을 바꾸는 것 뿐 아니라 리스트에서는 슬라이스 연산을 이용해서 여러 개의 요소를 한꺼번에 바꿀 수도 있다.

>>> a_list = ['a', 'b', 'c', 'd', 'e', 'f']
>>> a_list[1:3] = ['x', 'y']
>>> a_list ['a', 'x', 'y', 'd', 'e', 'f']

이 슬라이스에 지정하는 기능을 이용하면 다음과 같이 리스트에서 슬라이스 부분에 빈 리스트를 지정하면 그 요소들을 리스트에서 제거하는 것도 가능하다.

>>> a_list = ['a', 'b', 'c', 'd', 'e', 'f']
>>> a_list[1:3] = []
>>> a_list ['a', 'd', 'e', 'f']

또한 리스트에 슬라이스로 표시된 자리에 원하는 만큼 집어넣으면 요소를 추가하는 것도 가능하다.

>>> a_list = ['a', 'd', 'f']
>>> a_list[1:1] = ['b', 'c']
>>> a_list ['a', 'b', 'c', 'd', 'f']
>>> a_list[4:4] = ['e']
>>> a_list ['a', 'b', 'c', 'd', 'e', 'f']

3.8. List 삭제

슬라이스를 이용해 리스트의 요소를 삭제하는 것은 좀 어색하고, 그래서 오류가 날 가능성이 많다. 파이썬에서는 요소의 삭제를 위해 좀더 나은 방법을 제공한다.

>>> a = ['one', 'two', 'three']
>>> del a[1]
>>> a
['one', 'three']

위의 코드에서 보듯이 del 연산은 인덱스를 받아서 해당 요소를 삭제하는데, 없는 인덱스가 오면 런타임 오류가 발생한다.

del 연산에서 슬라이스도 이용할 수 있다.

>>> a_list = ['a', 'b', 'c', 'd', 'e', 'f']
>>> del a_list[1:5]
>>> a_list ['a', 'f']

앞에서도 살펴봤지만 슬라이스는 앞의 인덱스 요소는 포함하고 두번째 인덱스 요소는 포함하지 않는다. 여기서는 ‘b’부터 ‘e’까지가 삭제되었다.

3.9. List 메소드들

countindex 외에도 리스트는 여러 가지 유용한 메소드들을 가지고 있다. 리스트는 변경가능하기 때문에 이러한 메소드들은 호출된 리스트를 직접 변경시키기도 한다. 또한 어떤 메소드는 변경된 값을 새로운 리스트로 리턴하기도 한다.

>>> mylist = []
>>> mylist.append('this')
>>> mylist
['this']
>>> mylist.append('that')
>>> mylist
['this', 'that']

append 메소드는 리스트에 요소를 추가하는 메소드다. 매개변수는 한 개 있는데 그 값이 적용된 리스트의 마지막 원소로 추가된다.

>>> mylist.insert(1, 'thing')
>>> mylist
['this', 'thing', 'that']
>>> mylist.remove('thing')
>>> mylist
['that', 'this']

mylist에 값을 추가하는 방법으로 insert는 원하는 위치에 추가할 수 있다. 위의 예제는 1의 자리, 즉 두 번째 값으로 ‘thing’을 추가했다. remove() 메소드는 매개변수로 주어진 값을 리스트에서 삭제한다. insert에서 주어진 인덱스가 너무 크면(없으면) 제일 끝에 붙이고, remove에서 해당되는 값이 없으면 오류를 일으킨다.

>>> mylist.sort()
>>> mylist
['that', 'thing', 'this']
>>> mylist.reverse()
>>> mylist
['this', ‘thing’, 'that']

리스트의 sort 메소드는 특히 유용하다. 파이썬이 리스트에 있는 요소들을 알아서 소트해 준다. 정렬할 때는 해당하는 타입의 요소들에 대한 비교 연산 <를 사용한다. 만약 두 요소가 타입이 달라서 <를 취할 수 없으면 오류가 난다. 정렬 결과로 그 리스트의 값의 순서가 바뀐다. reverse()는 리스트의 요소의 순서를 뒤집어서 제일 끝에 있던 것이 제일 앞에 나오도록 바꾼다.

3.10. 변수 이름과 변경되는 값의 관계

리스트를 가리키는 변수들 간의 관계를 살펴보자. 다음의 지정문을 수행했다고 가정해 보자.

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

ab라는 이름은 둘다 1, 2, 3을 가진 리스트를 가리킨다. 그러나 그들이 같은 리스트를 가리키는지는 알지 못한다.

두 변수가 가지는 상태는 다음 그림과 같이 두 가지 경우가 있을 것이다.

또는

왼쪽은 변수 ab가 같은 값을 가지는 다른 두 객체를 가리키고 있고 오른쪽 경우는 같은 객체를 가리키고 있다. 실제로 왼쪽은 리스트 객체가 두 개 생성되었고 오른쪽은 하나만 생긴다.

두 변수가 같은 값을 가지고 있는지, 즉 내용이 같은가는 ==를 이용해 검사할 수 있다.

>>> a == b
True

반면에 두 변수가 같은 객체를 가리키고 있는지는 is 연산을 통해 확인할 수 있다. 즉 가리키고 있는 대상이 같은 것인가를 나타낸다.

>>> a is b
False

이것은 ab가 같은 객체를 가리키고 있지 않다는 것을 알려주고 있다. 즉 위의 그림에서 왼쪽과 같은 상태임을 알려준다.

3.11. 별칭(Aliasing)

변수는 객체를 가리키므로 우리가 한 변수에 다른 변수를 지정하면 두 변수는 같은 객체를 가리키게 된다.

>>> a = [1, 2, 3]
>>> b = a
>>> a is b
True

이 경우, 변수 간의 관계가 위 그림에서 오른쪽에 해당한다.

같은 리스트가 두 가지 다른 이름을 가지므로 이런 경우를 우리는 별칭이라고 한다. 리스트는 변경가능하기 때문에 한 변수에서 요소를 바꾸면 그것이 다른 변수의 값에도 영향을 미친다.

>>> b[0] = 5
>>> a
[5, 2, 3]

이러한 성질이 유용할 때도 있지만 어떤 때는 예측하기 어렵고 원하지 않는 결과를 낳는다(헷갈릴 수 있다). 예를 들어 어떤 리스트를 여러 개의 변수가 동시에 가리키고 있을 때(별칭도 생기고 매개변수로 보내기도 하고 등등) 그 리스트의 값이 바뀌어 버리면 그 변수들의 값이 모두 바뀌게 된다. 일반적으로 이렇게 값을 변경되는 리스트는 별칭을 만들지 않는 것이 좋다. 스트링이나 튜플 같은 불변 객체라면 일단 생성되고 나서는 값이 변경되지 않으므로 얼마든지 다른 변수에 지정해도 그런 문제가 없을 것이다.

3.12. 리스트 클론하기

리스트를 변경하면 위에서 살펴본 별칭의 문제가 생긴다. 그러므로 리스트를 변경하면서 원본도 그대로 유지하고 싶다면 우리는 리스트의 사본을 만들 수 있어야 한다. 다른 변수가 같은 리스트를 참조하는 것이 아니라 사본을 만들어서 다른 객체를 참조하게 해야 한다. 이런 과정을 우리는 흔히 클로닝이라고 한다.

리스트를 클론하는 가장 쉬운 방법은 슬라이스 연산을 쓰는 것이다.

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

사실 리스트에 슬라이스를 취하면 새로운 리스트가 만들어진다. a[:]는 리스트 전체의 슬라이스를 취한 것이다.

이제 우리는 b릐 값을 바꿔도 a가 바뀔까 걱정할 필요가 없다.

>>> b[0] = 5
>>> a
[1, 2, 3]

3.13. 중첩 리스트

중첩 리스트는 다른 리스트의 요소로 리스트가 들어간 경우다. 다음 리스트는 인덱스 3의 요소가 중첩된 리스트다.

>>> nested = ["hello", 2.0, 5, [10, 20]]

nested[3]을 출력하면 [10, 20]가 찍힐 것이다. 이 중첩 리스트에서 한 요소를 꺼내고 싶다면 두 단계로 인덱스를 써주어야 한다.

>>> elem = nested[3]
>>> elem[0]
10

또는 다음과 같이 인덱스를 두 번 쓸 수도 있다.

>>> nested[3][1]
20

대괄호 연산은 왼쪽부터 오른쪽으로 계산한다. 그러므로 이 수식은 먼저 nested 리스트의 3번째 요소를 꺼낸 후 거기서 1번째 요소를 가져오게 된다.

3.14. 스트링과 리스트

파이썬은 스트링의 리스트를 스트링으로 바꾸거나 스트링을 여러 개 스트링의 리스트로 바꾸어 주는 기능을 제공한다.

list() 함수는 임의의 시퀀스 타입을 받아서 각 요소들을 원소로 가지는 리스트로 바꾸어 준다. 스트링에 적용하면 각 글자의 리스트를 얻게 된다.

>>> list("Crunchy Frog") 
['C', 'r', 'u', 'n', 'c', 'h', 'y', ' ', 'F', 'r', 'o', 'g']

split 메소드는 스트링에 대해서 불려져서 스트링을 분리해서 스트링의 리스트로 돌려준다. 이 때 원래 스트링을 지정된 분리자(delimiter)를 기준으로 나눈다. 디폴트 분리자는 공백문자다. 공백문자란 빈칸, , 줄바꿈 등이다.

>>> "Crunchy frog covered in dark, bittersweet chocolate".split()
['Crunchy', 'frog', 'covered', 'in', 'dark,', 'bittersweet', 'chocolate']

다음 예에서는 'o'를 기준으로 분리하는 예를 보여준다.

>>> "Crunchy frog covered in dark, bittersweet chocolate".split('o')
['Crunchy fr', 'g c', 'vered in dark, bittersweet ch', 'c', 'late']

분리자 값인 o는 결과 리스트에는 나타나지 않게 된다.

join 메소드는 split 메소드의 반대방향이라고 할 수 있다. 스트링의 리스트를 매개변수로 받아서 그 요소들을 결합하여 만들어진 하나의 스트링을 반환한다. 다음 예는 빈칸을 이용하여 매개변수 리스트의 여러 스트링을 이어 붙인 스트링을 반환한다.

>>> ' '. join(['crunchy', 'raw', 'unboned', 'real', 'dead', 'frog'])
'crunchy raw unboned real dead frog'

join 메소드 앞에 있는 스트링이 리스트에 있는 요소들을 이어 붙일 때 분리자처럼 역할을 해서 결과 스트링을 돌려준다.

>>> '**'.join(['crunchy', 'raw', 'unboned', 'real', 'dead', 'frog']) 
'crunchy**raw**unboned**real**dead**frog'

분리자는 빈스트링이 될 수도 있다. 그럼 스트링들을 그냥 이어 붙이게 된다.

>>> ''.join(['crunchy', 'raw', 'unboned', 'real', 'dead', 'frog']) 
'crunchyrawunbonedrealdeadfrog'

3.15. 튜플 지정

가끔 두 변수의 값을 바꾸는 것이 필요할 때가 있다. 일반적인 지정문으로는 두 변수의 값을 바꾸기 위해 임시 변수를 써서 값을 저장해 둔 후 변수의 값을 바꾸어야 한다.

temp = a
a = b
b = temp

이런 일을 자주 해야 된다면 이 방법은 불편할 수 있다. 파이썬은 이런 문제를 편리하게 해결할 수 있는 튜플 지정문을 제공한다. 이것은 사실은 괄호가 생략된 튜플의 지정이다.

a, b = b, a

왼쪽은 변수의 튜플이고 오른쪽은 값의 튜플이다. 각 값은 대응하는 변수에 지정된다. 오른쪽의 모든 수식은 지정이 일어나기 전에 모두 계산된다. 이 기능은 튜플 지정이 매우 유용하게 쓰일 수 있게 해 준다.

당연히 좌측의 변수 개수는 우측의 값의 개수와 같아야 한다.

>>> a, b, c, d = 1, 2, 3
ValueError: need more than 3 values to unpack

튜플 지정은 함수의 반환값에서도 유용하게 쓰인다. 여러 개의 값을 반환하는 함수나 메소드의 반환값을 다음과 같이 받을 수 있다. 다음과 같이 라스트 요소의 최대와 최소를 동시에 돌려주는 메소드를 생각해 볼 수 있다.

mx, mn = mylist.get_max_min()
더보기

▣  헷갈려요

튜플은 변경할 수 없다고 했는데, 그것이 무슨 의미일까? 다음 코드를 보면 a[0] = 4는 타입 오류가 난다. 튜플에서 아이템 지정은 허용되지 않는다라는 오류 메시지가 나온다.

그런데 a[0].append(6)은 불평없이 잘 실행되었는데 그 이유가 무엇일까? a[0] =은 요소를 변경하는 것이지만 a[0].append는 튜플의 요소는 바뀌지 않고 그 요소의 값이 바뀌는 것이다. 튜플 입장에서는 a[0]에서 가리키는 리스트 객체가 다른 것으로 바뀌는 것은 안되지만 그 요소가 내부적으로 데이터를 추가하는 것은? 그것은 그 요소에서 알아서 할 일이므로 상관없는 것이다. 즉 요소의 지정은 안 되지만, 요소가 내부적으로 연산을 취하는 것은 문제가 없다. 그럼 >>> a[1] += 4 라는 문장은 어떨까? 이것은 5가 내부적으로 9로 바뀐 것이라고 볼 수 있을까? 여기서 또 int 타입의 불변(immutable) 성질이 등장한다. 모든 숫자는 그 자체로 하나의 객체이고 다른 숫자가 되면 다른 객체가 된다. 사실 +=은 지정문을 줄여서 표현한 것일 뿐이므로 이것도 지정이다. 즉 튜플에서 요소가 바뀔 수 없다는 것은 요소를 지정할 수 없다라고 보면 된다. 

>>> ([3], 5, 6)
([3], 5, 6)
>>> a = ([3], 5, 6)
>>> a[0].append(6)
>>> a
([3, 6], 5, 6)
>>> a[0] = 4
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    a[0] = 4
TypeError: 'tuple' object does not support item assignment
>>> 
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함