Slicing 구문이 [0:4]처럼 되어 있을 때, 0번 인덱스부터 3번 인덱스까지의 모든 요소가 결과에 반영되는 것은 모두 step이 1로 설정되기 때문이다.
step은 보폭이라는 뜻을 가지고 있다. 의미 그대로 이해하면 되는데, 만약 step이 1이라면 한 칸씩 우측으로 참조해가며 결과에 반영, 2라면 두 칸씩 우측으로 참조해가며 결과에 반영하는 식이 된다. Slicing 구문에서 종료 인덱스의 뒤에 step을 명시할 수 있다. 다음은 그 예다.
l = [1, 2, 3, 4, 5]
print(l[0:4:1])
print(l[0:4:2])
결과
[1, 2, 3, 4]
[1, 3]
첫 번째 Slicing 구분의 [0:4:1]은 [0:4]와 동일한 의미다. step의 기본값이 1이기 때문이다. 0번 인덱스부터 3번 인덱스까지, 한 칸씩 우측으로 참조해가며 Slicing을 하므로 결과는 [1, 2, 3, 4]가 된다.
그 다음의 Slicing 구문 [0:4:2]는 0번 인덱스부터 3번 인덱스까지, 두 칸씩 우측으로 참조해가며 결과를 만든다. 따라서 0번, 2번 인덱스가 포함되므로 결과는 [1, 3]이 된다.
step 개념이 들어가게 되면 Slicing 구문을 만들기가 더 어려워진다. 예로 다음 예제의 Slicing 구문은 1번 인덱스부터 끝까지 2의 step을 둔다는 의미를 가진다.
l = [1, 2, 3, 4, 5]
print(l[1::2])
결과
[2, 4]
예제의 [1::2]와 같이 step이 포함되고, 특정 부분이 생략되는 식의 문법은 쉽게 만들기 어렵다. 조금 쉽게 하려면, 범위를 지정하는 것과 step을 지정하는 것을 따로 생각하면 된다. 다음은 그 예다.
l = [1, 2, 3, 4, 5]
# 1. 무조건 콜론 하나를 입력하고 시작한다.
l[:]
# 2. 여기에 시작과 종료 인덱스가 필요하다면 명시한다. 여기까지 하면 범위 지정이 끝난다.
l[1:]
# 3. step이 필요하다면, 콜론과 함께 붙여준다.
print(l[1::2])
결과
[2, 4]
step의 부호는 Slicing 대상을 읽어나가는 방향을 결정한다. step이 양수로 설정되면 시작 인덱스부터 종료 인덱스까지 오른쪽 방향으로, 음수로 설정되면 왼쪽 방향으로 읽어나가며 결과를 만든다. 다음은 step으로 음수를 사용한 예다.
l = [1, 2, 3, 4, 5]
print(l[4:0:-1])
print(l[4:0:-2])
결과
[5, 4, 3, 2]
[5, 3]
따라서 음수 step을 사용할 때는, 시작 인덱스가 종료 인덱스보다 순서 상 뒤쪽에 위치하는 값이어야 한다.
l = [1, 2, 3, 4, 5]
print(l[0:4:-1])
print(l[4:0:-1])
결과
[]
[5, 4, 3, 2]
다음 코드의 실행 결과를 예상해보자.
l = [1, 2, 3, 4, 5, 6]
print(l[0:5:2])
print(l[0:-1:1])
print(l[1::2])
print(l[:-1:2])
print(l[::2])
다음 코드의 실행 결과를 예상해보자.
l = [1, 2, 3, 4, 5, 6]
print(l[5:0:-1])
print(l[5:0:-2])
print(l[0:5:-2])
step은 0으로 설정할 수 없다.
l = [1, 2, 3, 4, 5]
print(l[::0])
결과
Traceback (most recent call last):
File "example.py", line 3, in <module>
print(l[::0])
ValueError: slice step cannot be zero
음수 step은 반대로 읽기 동작이라고 볼 수 있으므로, Sequence를 뒤집는 데에 응용할 수 있다.
l = [1, 2, 3, 4, 5]
print(l[::-1])
결과
[5, 4, 3, 2, 1]
이런 Sequence 뒤집기를 응용해 풀 수 있는 문제로 회문(palindrome) 검사가 있다. 회문은 앞에서부터 읽은 결과와, 뒤에서부터 거꾸로 읽은 결과가 동일한 문자열을 말한다. 예로 a나 level은 회문이고, xyz는 회문이 아니다.
앞에서의 예와 같이 step을 -1로 두면 연속열을 뒤집을 수 있으므로, 슬라이싱의 결과와 원본이 서로 같은지 검사하는 것으로 문제를 풀 수 있다. 다음은 문자열을 입력받아 회문 여부를 출력하는 예제다.
여기에는 뒤에서 배울 비교 연산자인 ==가 포함되어 있다. a == b는 a와 b의 값이 동일한지의 여부를 bool 타입의 결과로 평가된다고 생각하면 된다. 예로 1 == 1은 True로, 1 == 2는 False로 평가된다.
x = input()
result = x == x[::-1]
print(result)
입력
level
결과
True
입력
xyz
결과
False
Slicing에서 인덱스의 명시가 생략되면, 시작 인덱스는 0으로, 종료 인덱스는 Sequence의 길이로 처리된다고 설명했다. 만약 step이 음수일 때도 이 규칙이 그대로 적용되었다면, [::-1]은 [0:5:-1]로 처리되어 결과가 []이 될 것이다. 그러나 Slicing에서 인덱스의 기본값은 step의 부호에 의존하기 때문에, 자연스럽게 잘 동작하는 것을 볼 수 있다.
l = [1, 2, 3, 4, 5]
print(l[::-1])
결과
[5, 4, 3, 2, 1]
다음은 기본값 처리 알고리즘을 정리한 것이다.
l = [1, 2, 3, 4, 5]
# 아래 두 줄은 같은 의미
print(l[::1])
print(l[0:5:1])
# 아래 두 줄은 같은 의미
print(l[::-1])
print(l[5:-6:-1])
결과
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]
[5, 4, 3, 2, 1]
step이 음의 정수일 때, 종료 인덱스는 사실 -1로 처리된다. 이 -1은 파이썬의 음수 인덱스 개념이 아니라, C언어 수준에서 0번 인덱스의 앞을 의미하는 논리적인 값이다. 본문에서는 -1로 설명하면 헛갈릴 수 있어, 쉽게 이해할 수 있도록 -(Sequence의 길이) - 1로 표현했다.
결론적으로, Slicing은 다음과 같은 흐름으로 동작한다.