[파이썬 튜토리얼] Indexing과 Slicing

PlanB·2023년 5월 31일
1

파이썬 튜토리얼

목록 보기
18/21

Level 1

앞의 두 단원을 통해 list와 tuple에 대해서 알아봤다. 이 둘은 요소들이 모두 순서를 가지고 나열되어 있기 때문에, Sequence라는 범주에 포함시키곤 한다. 이렇게 Sequence에 해당하는 타입들은 0으로 시작하는 정수 번호가 순차적으로 부여된다. 이러한 순서를 인덱스라고 부른다.

l = [
  15,  # 0번 인덱스
  32,  # 1번 인덱스
  66   # 2번 인덱스
]

Sequence의 각 요소에 접근하기 위해 인덱스를 사용할 수 있다.

Sequence가 되기 위해서는, 파이썬에서 정해 둔 몇 가지 요구사항을 만족해야 한다. 당장은 이해하기 어려우므로, 순서를 통해 요소에 접근할 수 있는 타입을 sequence라고 생각하면 된다.

Indexing

인덱스를 통해 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은 한 번 정의되고 나면 수정이 불가능하다는 특징을 가진다.

Slicing

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])

Level 2

Index에 해당하는 요소가 대상에 없을 때

명시한 인덱스가 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으로 두기

Slicing에서 인덱스가 정의될 자리에 None이 명시되어 있다면, 그 부분이 시작 인덱스의 자리인지, 종료 인덱스의 자리인지에 따라서 적절한 값으로 치환된다.

  • 시작 인덱스의 위치에 None이 명시되면, 0으로 처리한다.
  • 종료 인덱스의 위치에 None이 명시되면, Sequence의 길이에 해당하는 값으로 처리한다.
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]

Slice에 대해 값 할당하기

앞에서, 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]

Level 3

Slicing을 이용한 shallow copy

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
profile
백엔드를 주로 다룹니다. 최고가 될 수 없는 주제로는 글을 쓰지 않습니다.

0개의 댓글