[파이썬 튜토리얼] 슬라이싱의 step

step, 음수 step, slicing 구분의 기본값, 회문 검사에 응용하기

·

4 min read

Level 1

step

Slicing 구문이 [0:4]처럼 되어 있을 때, 0번 인덱스부터 3번 인덱스까지의 모든 요소가 결과에 반영되는 것은 모두 step1로 설정되기 때문이다.

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]이 된다.

Slicing 구문 만들기

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

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]

조언

  • Slicing의 step 개념은 읽고 이해할 수 있을 정도만 되면 괜찮다. 직접 쓸 일은 자주 없을 것이다.

연습문제

결과 예상하기 1

다음 코드의 실행 결과를 예상해보자.

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

결과


결과 예상하기 2

다음 코드의 실행 결과를 예상해보자.

l = [1, 2, 3, 4, 5, 6]

print(l[5:0:-1])
print(l[5:0:-2])
print(l[0:5:-2])

결과


자투리 지식

  • step은 stride라고도 부른다. 파이썬에서 공식적으로 사용하는 명칭은 step이다.

Level 2

[::0]

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을 응용하기

음수 step은 반대로 읽기 동작이라고 볼 수 있으므로, Sequence를 뒤집는 데에 응용할 수 있다.

l = [1, 2, 3, 4, 5]

print(l[::-1])

결과

[5, 4, 3, 2, 1]

응용

이런 Sequence 뒤집기를 응용해 풀 수 있는 문제로 회문(palindrome) 검사가 있다. 회문은 앞에서부터 읽은 결과와, 뒤에서부터 거꾸로 읽은 결과가 동일한 문자열을 말한다. 예로 alevel은 회문이고, xyz는 회문이 아니다.

앞에서의 예와 같이 step을 -1로 두면 연속열을 뒤집을 수 있으므로, 슬라이싱의 결과와 원본이 서로 같은지 검사하는 것으로 문제를 풀 수 있다. 다음은 문자열을 입력받아 회문 여부를 출력하는 예제다.

여기에는 뒤에서 배울 비교 연산자인 ==가 포함되어 있다. a == ba와 b의 값이 동일한지의 여부를 bool 타입의 결과로 평가된다고 생각하면 된다. 예로 1 == 1True로, 1 == 2False로 평가된다.

x = input()

result = x == x[::-1]

print(result)

입력

level

결과

True

입력

xyz

결과

False

음수 step에서 Slicing 구문의 기본값

Slicing에서 인덱스의 명시가 생략되면, 시작 인덱스는 0으로, 종료 인덱스는 Sequence의 길이로 처리된다고 설명했다. 만약 step이 음수일 때도 이 규칙이 그대로 적용되었다면, [::-1][0:5:-1]로 처리되어 결과가 []이 될 것이다. 그러나 Slicing에서 인덱스의 기본값은 step의 부호에 의존하기 때문에, 자연스럽게 잘 동작하는 것을 볼 수 있다.

l = [1, 2, 3, 4, 5]

print(l[::-1])

결과

[5, 4, 3, 2, 1]

다음은 기본값 처리 알고리즘을 정리한 것이다.

  • step이 0이라면 에러가 발생한다.
  • step이 양의 정수라면 시작 인덱스의 기본값은 0, 종료 인덱스의 기본값은 Sequence의 길이로 처리한다.
  • step이 음의 정수라면 시작 인덱스의 기본값은 Sequence의 길이, 종료 인덱스의 기본값은 -(Sequence의 길이) - 1로 처리한다. 예를 들어 대상의 길이가 3이라면 -4로 처리한다.
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 동작의 흐름

결론적으로, Slicing은 다음과 같은 흐름으로 동작한다.

  1. step에 기본값 처리를 수행한다.
  2. 이 step을 통해 시작 인덱스(start), 종료 인덱스(stop)에 대한 기본값 처리를 수행한다.
  3. step에 따라 시작 인덱스부터 종료 인덱스까지 요소들을 읽어나가며 결과를 산출한다.