11.4 슬라이스 사용하기

시퀀스 자료형은 슬라이스라는 기능을 자주 사용합니다. 슬라이스(slice)는 무엇인가의 일부를 잘라낸다는 뜻인데, 시퀀스 슬라이스도 말 그대로 시퀀스 객체의 일부를 잘라냅니다.

  • 시퀀스객체[시작인덱스:끝인덱스]

다음은 리스트의 일부를 잘라서 새 리스트를 만듭니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[0:4]     # 인덱스 0부터 3까지 잘라서 새 리스트를 만듦
[0, 10, 20, 30]

[ ] 안에 시작 인덱스와 끝 인덱스를 지정하면 해당 범위의 리스트를 잘라서 가져올 수 있습니다. 여기서 주의할 점이 있는데, 끝 인덱스는 가져오려는 범위에 포함되지 않습니다. 따라서 끝 인덱스는 실제로 가져오려는 인덱스보다 1을 더 크게 지정해야 합니다.

그림 11-18 리스트 슬라이스

예를 들어 요소가 10개 들어있는 리스트를 처음부터 끝까지 가져오려면 [0:9]가 아닌 [0:10]이라야 합니다(끝 인덱스는 범위를 벗어난 인덱스를 지정할 수 있습니다).

>>> a[0:10]    # 인덱스 0부터 9까지 잘라서 새 리스트를 만듦
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
그림 11-19 리스트를 처음부터 끝까지 가져오기

다음과 같이 실행해보면 쉽게 이해할 수 있습니다. a[1:1]처럼 시작 인덱스와 끝 인덱스를 같은 숫자로 지정하면 아무것도 가져오지 않습니다. a[1:2]처럼 끝 인덱스에 1을 더 크게 지정해야 요소 하나를 가져옵니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[1:1]    # 인덱스 1부터 0까지 잘라서 새 리스트를 만듦
[]
>>> a[1:2]    # 인덱스 1부터 1까지 잘라서 새 리스트를 만듦
[10]
그림 11-20 시작 인덱스와 끝 인덱스가 같을 때, 끝 인덱스에 1을 더 크게 지정했을 때

정리하자면 슬라이스를 했을 때 실제로 가져오는 요소는 시작 인덱스부터 끝 인덱스 - 1까지입니다.

11.4.1  리스트의 중간 부분 가져오기

그럼 리스트의 중간 부분을 가져오는 방법을 자세히 알아보겠습니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[4:7]     # 인덱스 4부터 6까지 요소 3개를 가져옴
[40, 50, 60]

a[4:7]은 리스트 a 중간에 있는 인덱스 4부터 6까지 요소 3개를 가져옵니다.

그림 11-21 리스트의 중간 부분 가져오기

특히 슬라이스는 a[4:-1]과 같이 음수를 인덱스로 지정할 수도 있습니다.

>>> a[4:-1]    # 인덱스 4부터 -2까지 요소 5개를 가져옴
[40, 50, 60, 70, 80]

인덱스에서 -1은 뒤에서 첫 번째 요소를 뜻한다고 했죠? 끝 인덱스는 가져오려는 인덱스보다 1을 더 크게 지정한다고 했으므로 실제로는 뒤에서 두 번째(인덱스 -2) 요소인 80까지만 가져옵니다(음수는 숫자가 작을 수록 큰 수입니다. 그래서 -1은 -2보다 1이 더 큽니다).

그림 11-22 음수 인덱스를 지정하여 리스트의 중간 부분 가져오기

11.4.2  인덱스 증가폭 사용하기

지금까지 지정된 범위의 요소를 모두 가져왔죠? 슬라이스는 인덱스의 증가폭을 지정하여 범위 내에서 인덱스를 건너뛰며 요소를 가져올 수 있습니다.

다음은 인덱스를 3씩 증가시키면서 요소를 가져옵니다. 여기서 주의할 점은 인덱스의 증가폭이지 요소의 값 증가폭이 아니라는 점입니다.

  • 시퀀스객체[시작인덱스:끝인덱스:인덱스증가폭]
>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[2:8:3]    # 인덱스 2부터 3씩 증가시키면서 인덱스 7까지 가져옴
[20, 50]

a[2:8:3]을 실행하니 [20, 50]이 나왔죠? 왜 이런 결과가 나왔을까요? 먼저 시작 인덱스가 2이므로 20부터 가져옵니다. 그리고 인덱스 증가폭을 3으로 지정했으므로 인덱스 5의 50, 인덱스 8의 80을 가져올 수 있습니다. 하지만, 끝 인덱스를 8로 지정했으므로 인덱스 7까지만 가져옵니다. 따라서 20과 50만 가져와서 [20, 50]이 나옵니다.

그림 11-23 인덱스 증가폭 사용하기

인덱스 증가폭을 지정하더라도 가져오려는 인덱스(끝 인덱스 - 1)를 넘어설 수 없다는 점을 꼭 기억해두세요.

만약 끝 인덱스 - 1과 증가한 인덱스가 일치한다면 해당 요소까지 가져올 수 있습니다. 다음은 끝 인덱스를 9로 지정하여 인덱스 8의 80까지 가져옵니다. 따라서 [20, 50, 80]이 나옵니다.

>>> a[2:9:3]    # 인덱스 2부터 3씩 증가시키면서 인덱스 8까지 가져옴
[20, 50, 80]
그림 11-24 끝 인덱스 - 1과 증가한 인덱스가 일치하는 경우

11.4.3  인덱스 생략하기

슬라이스를 사용할 때 시작 인덱스와 끝 인덱스를 생략할 수도 있습니다. 인덱스를 생략하는 방법은 시퀀스 객체의 길이를 몰라도 되기 때문에 자주 쓰이는 방식입니다. 주로 시퀀스 객체의 마지막 일부분만 출력할 때 사용합니다.

리스트 a에서 a[:7]과 같이 시작 인덱스를 생략하면 리스트의 처음부터 끝 인덱스 - 1(인덱스 6)까지 가져옵니다.

  • 시퀀스객체[:끝인덱스]
>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[:7]    # 리스트 처음부터 인덱스 6까지 가져옴
[0, 10, 20, 30, 40, 50, 60]
그림 11-25 시작 인덱스 생략하기

그리고 a[7:]과 같이 끝 인덱스를 생략하면 시작 인덱스(인덱스 7)부터 마지막 요소까지 가져옵니다.

  • 시퀀스객체[시작인덱스:]
>>> a[7:]    # 인덱스 7부터 마지막 요소까지 가져옴
[70, 80, 90]
그림 11-26 끝 인덱스 생략하기

또는, a[:]와 같이 시작 인덱스와 끝 인덱스를 둘다 생략하면 리스트 전체를 가져옵니다.

  • 시퀀스객체[:]
>>> a[:]     # 리스트 전체를 가져옴
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
그림 11-27 시작 인덱스와 끝 인덱스 둘다 생략하기

11.4.4  인덱스를 생략하면서 증가폭 사용하기

여기서 시작 인덱스 또는 끝 인덱스를 생략하면서 인덱스 증가폭을 지정하면 어떻게 될까요?

리스트 a에서 a[:7:2]와 같이 시작 인덱스를 생략하면서 인덱스 증가폭을 2로 지정하면 리스트의 처음부터 인덱스를 2씩 증가시키면서 끝 인덱스 - 1(인덱스 6)까지 요소를 가져옵니다.

  • 시퀀스객체[:끝인덱스:증가폭]
>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[:7:2]    # 리스트의 처음부터 인덱스를 2씩 증가시키면서 인덱스 6까지 가져옴
[0, 20, 40, 60]
그림 11-28 시작 인덱스를 생략하면서 인덱스 증가폭 지정하기

그리고 a[7::2]와 같이 끝 인덱스를 생략하면서 인덱스 증가폭을 2로 지정하면 시작 인덱스(인덱스 7)부터 인덱스를 2씩 증가시키면서 리스트의 마지막 요소까지 가져옵니다.

  • 시퀀스객체[시작인덱스::증가폭]
>>> a[7::2]    # 인덱스 7부터 2씩 증가시키면서 리스트의 마지막 요소까지 가져옴
[70, 90]
그림 11-29 끝 인덱스를 생략하면서 인덱스 증가폭 지정하기

또는, a[::2]와 같이 시작 인덱스와 끝 인덱스를 둘다 생략하면서 인덱스 증가폭을 2로 지정하면 리스트 전체에서 인덱스 0부터 2씩 증가하면서 요소를 가져옵니다.

  • 시퀀스객체[::증가폭]
>>> a[::2]     # 리스트 전체에서 인덱스 0부터 2씩 증가시키면서 요소를 가져옴
[0, 20, 40, 60, 80]
그림 11-30 시작 인덱스와 끝 인덱스를 둘다 생략하면서 인덱스 증가폭 지정하기

a[:7:2]a[7::2]는 2씩 증가한 인덱스와 끝 인덱스 - 1이 일치하여 지정된 범위에 맞게 요소를 가져왔습니다. 하지만, a[::2]는 끝 인덱스가 9이므로 인덱스가 2씩 증가하더라도 8까지만 증가할 수 있습니다. 따라서 인덱스 0, 2, 4, 6, 8의 요소를 가져옵니다.

만약 시작 인덱스, 끝 인덱스, 인덱스 증가폭을 모두 생략하면 어떻게 될까요?

  • 시퀀스객체[::]
>>> a[::]    # 리스트 전체를 가져옴
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]

그냥 리스트 전체를 가져옵니다. 즉, a[:]a[::]는 결과가 같습니다.

참고 | 슬라이스의 인덱스 증가폭을 음수로 지정하면?

슬라이스를 사용할 때 인덱스 증가폭을 음수로 지정하면 요소를 뒤에서부터 가져올 수 있습니다. 다음은 리스트 a에서 인덱스 5부터 2까지 1씩 감소시키면서 요소를 가져옵니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[5:1:-1]
[50, 40, 30, 20]

여기서 주의할 점은 인덱스가 감소하므로 끝 인덱스보다 시작 인덱스를 더 크게 지정해야 한다는 점입니다. 즉, a[5:1:-1]과 같이 시작 인덱스부터 끝 인덱스까지 감소하도록 지정합니다. 그리고 끝 인덱스는 가져오려는 범위에 포함되지 않습니다

특히 다음과 같이 시작 인덱스와 끝 인덱스를 생략하면서 인덱스 증가폭을 -1로 지정하면 어떻게 될까요? 이때는 리스트 전체에서 인덱스를 1씩 감소시키면서 요소를 가져오므로 리스트를 반대로 뒤집습니다.

>>> a[::-1]
[90, 80, 70, 60, 50, 40, 30, 20, 10, 0]

물론 이 방법은 리스트뿐만 아니라 모든 시퀀스 객체에 사용할 수 있습니다.

11.4.5  len 응용하기

이번에는 len을 응용하여 리스트 전체를 가져와보겠습니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[0:len(a)]    # 시작 인덱스에 0, 끝 인덱스에 len(a) 지정하여 리스트 전체를 가져옴
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[:len(a)]     # 시작 인덱스 생략, 끝 인덱스에 len(a) 지정하여 리스트 전체를 가져옴
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]

리스트 a의 요소는 10개입니다. 따라서 len(a)는 10이고, a[0:10]과 같습니다. 여기서 끝 인덱스는 가져오려는 인덱스보다 1을 더 크게 지정한다고 했으므로 len(a)에서 1을 빼지 않아야 합니다. 즉, 길이가 10인 리스트는 [0:10]이라야 리스트 전체를 가져옵니다.

그림 11-31 len으로 리스트 전체를 가져오기

11.4.6  튜플, range, 문자열에 슬라이스 사용하기

지금까지 리스트에서 슬라이스를 사용해봤습니다. 파이썬에서는 튜플, range, 문자열도 시퀀스 자료형이므로 리스트와 같은 방식으로 슬라이스를 사용할 수 있습니다.

먼저 튜플부터 잘라보겠습니다. 다음은 지정된 범위만큼 튜플을 잘라서 새 튜플을 만듭니다.

  • 튜플[시작인덱스:끝인덱스]
  • 튜플[시작인덱스:끝인덱스:인덱스증가폭]
>>> b = (0, 10, 20, 30, 40, 50, 60, 70, 80, 90)
>>> b[4:7]     # 인덱스 4부터 6까지 요소 3개를 가져옴
(40, 50, 60)
>>> b[4:]      # 인덱스 4부터 마지막 요소까지 가져옴
(40, 50, 60, 70, 80, 90)
>>> b[:7:2]    # 튜플의 처음부터 인덱스를 2씩 증가시키면서 인덱스 6까지 가져옴
(0, 20, 40, 60)
그림 11-32 튜플에 슬라이스 사용하기

'10.1 리스트 만들기'에서 range는 연속된 숫자를 생성한다고 했죠? range에 슬라이스를 사용하면 지정된 범위의 숫자를 생성하는 range 객체를 새로 만듭니다.

  • range객체[시작인덱스:끝인덱스]
  • range객체[시작인덱스:끝인덱스:인덱스증가폭]
>>> r = range(10)
>>> r
range(0, 10)
>>> r[4:7]     # 인덱스 4부터 6까지 숫자 3개를 생성하는 range 객체를 만듦
range(4, 7)
>>> r[4:]      # 인덱스 4부터 9까지 숫자 6개를 생성하는 range 객체를 만듦
range(4, 10)
>>> r[:7:2]    # 인덱스 0부터 2씩 증가시키면서 인덱스 6까지 숫자 4개를 생성하는 range 객체를 만듦
range(0, 7, 2)
그림 11-33 range에 슬라이스 사용하기

range는 리스트, 튜플과는 달리 요소가 모두 표시되지 않고 생성 범위만 표시됩니다. 이렇게 잘라낸 range 객체를 리스트로 만들려면 list에 넣으면 되겠죠?

>>> list(r[:7:2])
[0, 2, 4, 6]

문자열도 시퀀스 자료형이므로 슬라이스를 사용할 수 있습니다. 특히 문자열은 문자 하나가 요소이므로 문자 단위로 잘라서 새 문자열을 만듭니다.

  • 문자열[시작인덱스:끝인덱스]
  • 문자열[시작인덱스:끝인덱스:인덱스증가폭]
>>> hello = 'Hello, world!'
>>> hello[2:9]    # 인덱스 2부터 인덱스 8까지 잘라서 문자열을 만듦
'llo, wo'
>>> hello[2:]     # 인덱스 2부터 마지막 요소까지 잘라서 문자열을 만듦
'llo, world!'
>>> hello[:9:2]   # 문자열의 처음부터 인덱스를 2씩 증가시키면서 인덱스 8까지 잘라서 문자열을 만듦
'Hlo o'
그림 11-34 문자열에 슬라이스 사용하기

간단하죠? 이렇게 모든 시퀀스 자료형은 같은 방식으로 슬라이스를 사용해서 일부를 잘라낼 수 있습니다.

참고 | slice 객체 사용하기

파이썬에서는 slice 객체를 사용하여 시퀀스 객체(시퀀스 자료형으로 만든 변수)를 잘라낼 수도 있습니다.

슬라이스객체 = slice(끝인덱스)

슬라이스객체 = slice(시작인덱스, 끝인덱스)

슬라이스객체 = slice(시작인덱스, 끝인덱스, 인덱스증가폭)

시퀀스객체[슬라이스객체]

시퀀스객체.__getitem__(슬라이스객체)

다음과 같이 시퀀스 객체의 [ ](대괄호) 또는 __getitem__ 메서드에 slice 객체를 넣어주면 지정된 범위만큼 잘라내서 새 객체를 만듭니다.

>>> range(10)[slice(4, 7, 2)]
range(4, 7, 2)
>>> range(10).__getitem__(slice(4, 7, 2))
range(4, 7, 2)

물론 slice 객체를 하나만 만든 뒤 여러 시퀀스 객체에 사용하는 방법도 가능합니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> s = slice(4, 7)    # 인덱스 4부터 6까지 자르는 slice 객체 생성
>>> a[s]
[40, 50, 60]
>>> r = range(10)
>>> r[s]
range(4, 7)
>>> hello = 'Hello, world!'
>>> hello[s]
'o, '

11.4.7  슬라이스에 요소 할당하기

'11.3 인덱스 사용하기'에서 리스트에 인덱스로 접근하여 요소에 값을 할당해봤죠? 마찬가지로 시퀀스 객체는 슬라이스로 범위를 지정하여 여러 요소에 값을 할당할 수 있습니다.

  • 시퀀스객체[시작인덱스:끝인덱스] = 시퀀스객체

먼저 리스트를 만든 뒤 특정 범위의 요소에 값을 할당해보겠습니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[2:5] = ['a', 'b', 'c']    # 인덱스 2부터 4까지 값 할당
>>> a
[0, 10, 'a', 'b', 'c', 50, 60, 70, 80, 90]

a[2:5] = ['a', 'b', 'c']와 같이 리스트에 범위를 지정하고 다른 리스트를 할당했습니다. 이렇게 하면 인덱스 2부터 4까지 문자 'a', 'b', 'c'가 들어갑니다. 특히 이렇게 범위를 지정해서 요소를 할당했을 경우에는 원래 있던 리스트가 변경되며 새 리스트는 생성되지 않습니다.

그림 11-35 특정 범위에 요소 할당하기

a[2:5] = ['a', 'b', 'c']는 슬라이스 범위와 할당할 리스트의 요소 개수를 정확히 맞추었지만, 사실 개수를 맞추지 않아도 상관없습니다.

다음과 같이 요소 개수를 맞추지 않아도 알아서 할당됩니다. 만약 할당할 요소 개수가 적으면 그만큼 리스트의 요소 개수도 줄어듭니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[2:5] = ['a']    # 인덱스 2부터 4까지에 값 1개를 할당하여 요소의 개수가 줄어듦
>>> a
[0, 10, 'a', 50, 60, 70, 80, 90]
그림 11-36 슬라이스 범위보다 할당할 요소 개수가 적을 때

반면 할당할 요소 개수가 많으면 그만큼 리스트의 요소 개수도 늘어납니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[2:5] = ['a', 'b', 'c', 'd', 'e'] # 인덱스 2부터 4까지 값 5개를 할당하여 요소의 개수가 늘어남
>>> a
[0, 10, 'a', 'b', 'c', 'd', 'e', 50, 60, 70, 80, 90]
그림 11-37 슬라이스 범위와 할당할 요소의 개수가 다를 때

슬라이스는 인덱스 증가폭을 지정할 수 있었죠? 이번에는 인덱스 증가폭을 지정하여 인덱스를 건너뛰면서 할당해보겠습니다.

  • 시퀀스객체[시작인덱스:끝인덱스:인덱스증가폭] = 시퀀스객체
>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[2:8:2] = ['a', 'b', 'c']    # 인덱스 2부터 2씩 증가시키면서 인덱스 7까지 값 할당
>>> a
[0, 10, 'a', 30, 'b', 50, 'c', 70, 80, 90]

a[2:8:2] = ['a', 'b', 'c']와 같이 인덱스 2부터 2씩 증가시키면서 7까지 'a', 'b', 'c'를 할당합니다.

그림 11-38 인덱스 증가폭을 지정하여 요소 할당하기

단, 인덱스 증가폭을 지정했을 때는 슬라이스 범위의 요소 개수와 할당할 요소 개수가 정확히 일치해야 합니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> a[2:8:2] = ['a', 'b']
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    a[2:8:2] = ['a', 'b']
ValueError: attempt to assign sequence of size 2 to extended slice of size 3 

튜플, range, 문자열은 슬라이스 범위를 지정하더라도 요소를 할당할 수 없습니다.

>>> b = (0, 10, 20, 30, 40, 50, 60, 70, 80, 90)
>>> b[2:5] = ('a', 'b', 'c')
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    b[2:5] = ('a', 'b', 'c')
TypeError: 'tuple' object does not support item assignment 
>>> r = range(10)
>>> r[2:5] = range(0, 3)
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    r[2:5] = range(0, 3)
TypeError: 'range' object does not support item assignment 
>>> hello = 'Hello, world!'
>>> hello[7:13] = 'Python'
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    hello[7:13] = 'Python'
TypeError: 'str' object does not support item assignment 

11.4.8  del로 슬라이스 삭제하기

이번에는 시퀀스 객체의 슬라이스를 삭제해보겠습니다. 슬라이스 삭제는 다음과 같이 del 뒤에 삭제할 범위를 지정해주면 됩니다.

  • del 시퀀스객체[시작인덱스:끝인덱스]

다음은 리스트의 인덱스 2부터 4까지 요소를 삭제합니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> del a[2:5]    # 인덱스 2부터 4까지 요소를 삭제
>>> a
[0, 10, 50, 60, 70, 80, 90]

리스트 a에서 인덱스 2, 3, 4인 요소 20, 30, 40이 삭제되었습니다. 특히 del로 요소를 삭제하면 원래 있던 리스트가 변경되며 새 리스트는 생성되지 않습니다.

그림 11-39 슬라이스 삭제하기

인덱스 증가폭을 지정하면 인덱스를 건너뛰면서 삭제하겠죠? 다음은 인덱스 2부터 2씩 증가시키면서 인덱스 6까지 삭제합니다.

>>> a = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> del a[2:8:2]    # 인덱스 2부터 2씩 증가시키면서 인덱스 6까지 삭제
>>> a
[0, 10, 30, 50, 70, 80, 90]

인덱스 2, 4, 6인 요소 20, 40, 60이 삭제되었습니다.

그림 11-40 인덱스 증가폭을 지정하여 슬라이스 삭제하기

물론 튜플, range, 문자열은 del로 슬라이스를 삭제할 수 없습니다.

>>> b = (0, 10, 20, 30, 40, 50, 60, 70, 80, 90)
>>> del b[2:5]
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    del b[2:5]
TypeError: 'tuple' object does not support item deletion 
>>> r = range(10)
>>> del r[2:5]
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    del r[2:5]
TypeError: 'range' object does not support item deletion 
>>> hello = 'Hello, world!'
>>> del hello[2:9]
Traceback (most recent call last):
  File "<pyshell#12>", line 1, in <module>
    del hello[2:9]
TypeError: 'str' object does not support item deletion 

지금까지 시퀀스 자료형의 사용 방법을 알아보았습니다. 내용이 상당히 길었지만, 대부분 공통되면서 반복되는 내용입니다. 여기서는 시퀀스 자료형의 인덱스가 0부터 시작한다는 점이 가장 중요합니다. 그리고 슬라이스를 처음 접했을 때는 조금 헷갈릴 수 있습니다. 이 부분은 당장 이해하지 않아도 상관없습니다. 나중에 필요할 때 다시 돌아와서 학습하면 됩니다.