앞의 두 단원을 통해 list와 tuple에 대해서 알아봤다. 이 둘은 요소들이 모두 순서를 가지고 나열되어 있기 때문에, Sequence라는 범주에 포함시키곤 한다. 이렇게 Sequence에 해당하는 타입들은 0으로 시작하는 정수 번호가 순차적으로 부여된다. 이러한 순서를 인덱스라고 부른다.
l = [
15, # 0번 인덱스
32, # 1번 인덱스
66 # 2번 인덱스
]
Sequence의 각 요소에 접근하기 위해 인덱스를 사용할 수 있다.
Sequence가 되기 위해서는, 파이썬에서 정해 둔 몇 가지 요구사항을 만족해야 한다. 당장은 이해하기 어려우므로, 순서를 통해 요소에 접근할 수 있는 타입을 sequence라고 생각하면 된다.
인덱스를 통해 Sequence의 요소에 접근하는 것을 인덱싱(Indexing)이라고 한다. 다음은 그 예제다.
l = [1, 2, 3, 4, 5]
t = 1, 2, 3, 4, 5
print(l[0])
print(t[1])
결과
1
2
Sequence의 뒤에, 접근하고자 하는 요소의 번호를 대괄호로 감싸 명시하는 식이다. 이러한 Indexing을 할당문의 좌변에 사용할 수도 있다. 이는 해당 순서의 값을 변경하는 동작을 수행한다.
l = [1, 2, 3, 4, 5]
l[0] = 0
l[4] = 9
print(l)
결과
[0, 2, 3, 4, 9]
여기서 list와 tuple의 차이를 발견할 수 있다. 다음처럼 tuple의 내용에 수정을 가하려고 하면 에러가 발생한다.
t = 1, 2, 3, 4, 5
t[4] = 9
결과
Traceback (most recent call last):
File "example.py", line 2, in <module>
t[4] = 9
TypeError: 'tuple' object does not support item assignment
tuple은 한 번 정의되고 나면 수정이 불가능하다는 특징을 가진다.
Sequence에서 지정한 범위의 내용을 잘라낼 수 있다. Slicing이라 부른다.
l = [1, 2, 3, 4, 5]
t = 1, 2, 3, 4, 5
print(l[0:3])
print(t[1:3])
결과
[1, 2, 3]
(2, 3)
잘라내고자 하는 범위의 시작 인덱스와 종료 인덱스를 콜론(:)으로 구분해 작성하고, 대괄호로 감싸주면 된다. 이는 Sequence를 시작 인덱스부터 종료 인덱스의 이전 요소까지 잘라낸 결과로 평가한다.
다음 코드의 실행 결과를 예상해보자.
l = [1, 2, 3, 4, 5]
print(l[0])
print(l[2:4])
print(l[1:3][1])
명시한 인덱스가 Sequence에 존재하지 않는 경우, 에러가 발생한다.
l = [1, 2, 3]
print(l[3])
결과
Traceback (most recent call last):
File "example.py", line 3, in <module>
print(l[3])
IndexError: list index out of range
Slicing의 경우, 명시한 인덱스가 Sequence의 범위를 초과해도 에러가 발생하지 않는다. 접근할 수 없는 인덱스를 마주치는 경우 요소를 무시하도록 되어 있다.
l = [1, 2, 3]
print(l[0:10])
print(l[10:15])
결과
[1, 2, 3]
[]
Slicing에서 인덱스가 정의될 자리에 None이 명시되어 있다면, 그 부분이 시작 인덱스의 자리인지, 종료 인덱스의 자리인지에 따라서 적절한 값으로 치환된다.
l = [1, 2, 3, 4, 5]
# 아래 두 줄은 같은 의미
print(l[None:3])
print(l[0:3])
# 아래 두 줄은 같은 의미
print(l[0:None])
print(l[0:5])
결과
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
Slicing에서 인덱스를 생략할 수도 있다. 생략된 자리는 None으로 처리된다.
l = [1, 2, 3, 4, 5]
# 아래 두 줄은 같은 의미
print(l[None:3])
print(l[:3])
# 아래 두 줄은 같은 의미
print(l[0:None])
print(l[0:])
# 아래 두 줄은 같은 의미
print(l[None:None])
print(l[:])
None을 명시하기보다, 생략하는 용례가 더 잦다.
결과
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
앞에서, Indexing 표현식을 할당문의 좌변에 두는 코드를 알아봤었다. 이는 특정 순서에 해당하는 값을 변경하기 위해 사용된다. Slicing에 대해서도 이 개념을 적용할 수 있다. Slicing의 범위에 해당되는 부분이, 우변의 Sequence로 대체되는 식이다.
l = [1, 2, 3, 4, 5]
l[2:] = [4, 5, 6]
print(l)
결과
[1, 2, 4, 5, 6]
타입이 다른 경우에도 잘 처리된다. 좌변의 타입에 맞게 변환된다.
l = [1, 2, 3, 4, 5]
l[2:] = (4, 5, 6)
print(l)
결과
[1, 2, 4, 5, 6]
Slicing 범위와, 대체하고자 하는 Sequence 간 요소의 개수가 다르더라도 잘 처리된다.
l1 = [1, 2, 3, 4, 5]
l2 = [1, 2, 3, 4, 5]
l1[2:] = [4, 5]
l2[2:] = [4, 5, 6, 7]
print(l1)
print(l2)
결과
[1, 2, 4, 5]
[1, 2, 4, 5, 6, 7]
지정된 범위의 내용을 제거하는 데에도 응용할 수 있다.
l = [1, 2, 3, 4, 5]
l[2:] = []
print(l)
결과
[1, 2]
list에 대한 Slicing은 shallow copy를 생성한다.
l = [1, 2, 3, [4, 5]]
l_copy = l[:]
print(l is l_copy)
print(l[-1] is l_copy[-1])
결과
False
True
Immutable 타입인 tuple에는 적용되지 않는다.
t = (1, 2, 3)
t_copy = t[:]
print(t is t_copy)
결과
True