워로디스

Python. slice 정리 본문

개발/Python

Python. slice 정리

워로디스 2026. 6. 1. 22:15

1. 요약

Python에서 :는 단순한 기호가 아니라 슬라이스 표기법이다. 이 표기법은 내부적으로 slice 객체로 변환되어 리스트, 튜플, 문자열, NumPy 배열 등에서 범위 선택에 사용된다.

seq[start:stop:step]

의 의미는 다음과 같다.

start 이상부터 시작해서
stop 전까지 선택하고
step 간격으로 이동한다

중요한 점은 stop 위치의 원소는 포함되지 않는다는 것이다.

2. :는 표기법이고, slice는 객체다

코드에 보이는 :는 파이썬 문법의 일부다.

a[1:4]

이 표기는 내부적으로 대략 다음과 비슷하게 처리된다.

a.__getitem__(slice(1, 4, None))

즉 다음처럼 이해하면 정확하다.

코드에 보이는 :        -> 슬라이스 표기법
내부적으로 만들어지는 것 -> slice 객체

직접 slice 객체를 만들어서 사용할 수도 있다.

a = [10, 20, 30, 40, 50]

s = slice(1, 4)
a[s]
# [20, 30, 40]

3. 기본 형태: start:stop:step

슬라이스의 전체 형태는 다음과 같다.

seq[start:stop:step]

예시:

a = [10, 20, 30, 40, 50, 60]

print(a[1:4])     # [20, 30, 40]
print(a[1:5:2])   # [20, 40]

각 부분의 의미는 다음과 같다.

표현 의미
start 시작 인덱스
stop 끝 인덱스. 단, 포함되지 않음
step 이동 간격
a[1:4]

은 인덱스 1, 2, 3을 선택한다. 인덱스 4는 포함하지 않는다.

4. 생략 규칙

슬라이스에서는 start, stop, step을 생략할 수 있다.

a = [10, 20, 30, 40, 50]
표현 의미 결과
a[:] 전체 선택 [10, 20, 30, 40, 50]
a[:3] 처음부터 인덱스 3 전까지 [10, 20, 30]
a[2:] 인덱스 2부터 끝까지 [30, 40, 50]
a[1:4] 인덱스 1부터 4 전까지 [20, 30, 40]
a[::2] 처음부터 끝까지 2칸씩 [10, 30, 50]
a[1::2] 인덱스 1부터 끝까지 2칸씩 [20, 40]

생략된 값은 보통 다음처럼 해석된다.

start 생략 -> 처음부터
stop 생략  -> 끝까지
step 생략  -> 1칸씩

따라서 다음 둘은 같은 뜻이다.

a[:]
a[::]

둘 다 전체 범위를 선택한다.

5. 음수 인덱스와 슬라이스

Python에서는 음수 인덱스를 사용할 수 있다.

a = [10, 20, 30, 40, 50]

print(a[-1])   # 50
print(a[-2])   # 40

슬라이스에도 음수 인덱스를 쓸 수 있다.

print(a[-3:])     # [30, 40, 50]
print(a[:-1])     # [10, 20, 30, 40]
print(a[-4:-1])   # [20, 30, 40]

여기서도 stop은 포함되지 않는다.

a[-4:-1]

은 뒤에서 네 번째 원소부터 뒤에서 첫 번째 원소 전까지 선택한다.

6. 음수 step: 역방향 슬라이스

step에 음수를 쓰면 오른쪽에서 왼쪽으로 이동한다.

a = [10, 20, 30, 40, 50]

print(a[::-1])
# [50, 40, 30, 20, 10]

a[::-1]은 Python에서 매우 자주 쓰이는 관용구이며, 시퀀스를 뒤집는 의미다.

text = "hello"

print(text[::-1])
# 'olleh'

좀 더 복잡한 예:

a = [10, 20, 30, 40, 50, 60]

print(a[4:1:-1])
# [50, 40, 30]

해석:

인덱스 4에서 시작
인덱스 1 전까지 이동
step은 -1이므로 역방향

따라서 선택되는 인덱스는 4, 3, 2다. 인덱스 1은 포함되지 않는다.

7. 슬라이스는 원본을 복사하는가?

Python의 기본 리스트에서 슬라이스는 새 리스트를 만든다.

a = [10, 20, 30, 40]
b = a[:]

print(b)
# [10, 20, 30, 40]

print(a is b)
# False

a[:]는 전체 원소를 가진 새 리스트를 만든다. 그래서 리스트를 얕게 복사할 때 자주 쓰인다.

copy_a = a[:]

하지만 이것은 얕은 복사다. 내부에 중첩 리스트 같은 변경 가능한 객체가 있으면 내부 객체까지 깊게 복사하지는 않는다.

a = [[1, 2], [3, 4]]
b = a[:]

b[0][0] = 999

print(a)
# [[999, 2], [3, 4]]

바깥 리스트는 새로 만들어졌지만, 안쪽 리스트는 같은 객체를 공유한다.

8. 슬라이스에 대입하기

리스트에서는 슬라이스로 선택한 범위에 새 값을 대입할 수 있다.

a = [10, 20, 30, 40, 50]

a[1:4] = [200, 300]

print(a)
# [10, 200, 300, 50]

인덱스 1, 2, 3에 해당하던 [20, 30, 40][200, 300]으로 바뀐다.

길이가 달라도 된다.

a = [10, 20, 30]

a[1:2] = [100, 200, 300]

print(a)
# [10, 100, 200, 300, 30]

빈 리스트를 대입하면 해당 구간을 삭제하는 효과가 있다.

a = [10, 20, 30, 40]

a[1:3] = []

print(a)
# [10, 40]

9. 슬라이스로 삭제하기

del과 함께 슬라이스를 사용할 수 있다.

a = [10, 20, 30, 40, 50]

del a[1:4]

print(a)
# [10, 50]

특정 간격의 원소를 삭제할 수도 있다.

a = [10, 20, 30, 40, 50, 60]

del a[::2]

print(a)
# [20, 40, 60]

10. 리스트, 튜플, 문자열에서의 slice

slice는 NumPy 전용이 아니다. Python 내장 시퀀스 타입에서도 사용된다.

리스트

a = [10, 20, 30, 40]

print(a[1:3])
# [20, 30]

튜플

t = (10, 20, 30, 40)

print(t[1:3])
# (20, 30)

문자열

text = "abcdef"

print(text[1:4])
# 'bcd'

리스트는 변경 가능하지만, 튜플과 문자열은 변경 불가능하다. 그래서 튜플이나 문자열에는 슬라이스 대입을 할 수 없다.

text = "abcdef"

# text[1:3] = "XX"  # TypeError

11. slice() 함수로 직접 만들기

slice 객체는 직접 만들 수 있다.

s = slice(1, 4)

이것은 다음 표기와 같은 의미다.

1:4

단, 1:4는 인덱싱 문맥 안에서만 쓸 수 있는 표기법이다.

# s = 1:4  # SyntaxError

대신 이렇게 해야 한다.

s = slice(1, 4)

예:

a = [10, 20, 30, 40, 50]

s = slice(1, 4)

print(a[s])
# [20, 30, 40]

step까지 지정할 수도 있다.

s = slice(None, None, 2)

print(a[s])
# [10, 30, 50]

None은 생략을 뜻한다.

slice(None, None, None) -> [:]
slice(None, 3, None)    -> [:3]
slice(1, None, None)    -> [1:]
slice(None, None, 2)    -> [::2]
slice(1, 5, 2)          -> [1:5:2]

12. NumPy에서의 slice

NumPy에서도 같은 slice 객체를 사용한다. NumPy가 특별한 점은 여러 축(axis)에 대해 인덱싱을 동시에 할 수 있다는 것이다.

import numpy as np

A = np.array([
    [10, 11, 12, 13],
    [20, 21, 22, 23],
    [30, 31, 32, 33],
    [40, 41, 42, 43],
])

모든 행, 특정 열

A[:, 2]
# array([12, 22, 32, 42])

해석:

:  -> 모든 행
2  -> 2번 열

A[:, 2]는 “모든 행의 2번 열”이다.

특정 행, 모든 열

A[1, :]
# array([20, 21, 22, 23])

해석:

1  -> 1번 행
:  -> 모든 열

A[1, :]는 “1번 행의 모든 열”이다.

일부 행, 특정 열

A[:3, 2]
# array([12, 22, 32])

해석:

:3 -> 0번 행부터 3번 행 전까지
2  -> 2번 열

:::의 관계

A[:, 1]
A[::, 1]

두 표현은 같다.

::start, stop, step을 모두 생략한 형태이므로 전체 범위를 1칸씩 선택한다.

A[::, 1]

은 내부적으로 다음과 비슷하다.

A.__getitem__((slice(None, None, None), 1))

13. Python 기본 리스트와 NumPy 배열의 차이

Python 기본 리스트도 slice 객체를 사용한다.

lst = [10, 20, 30, 40]

lst[1:3]
# [20, 30]

하지만 기본 리스트는 NumPy처럼 다차원 인덱싱을 직접 지원하지 않는다.

lst = [
    [10, 11, 12],
    [20, 21, 22],
    [30, 31, 32],
]

# lst[:, 1]  # TypeError

기본 리스트에서 특정 열을 가져오려면 보통 리스트 컴프리헨션을 쓴다.

[row[1] for row in lst]
# [11, 21, 31]

NumPy는 다차원 배열을 지원하므로 다음처럼 쓸 수 있다.

A[:, 1]

정리하면:

slice 객체 자체     -> Python 내장
리스트의 a[1:3]     -> slice 객체 사용
문자열의 s[1:3]     -> slice 객체 사용
NumPy의 A[:, 1]     -> slice 객체 + 다차원 인덱싱 확장

14. 슬라이스와 차원 감소

NumPy에서 정수 인덱스를 쓰면 해당 축이 사라질 수 있다.

A[:, 2]

결과는 1차원 배열이다.

array([12, 22, 32, 42])

왜냐하면 열 방향에 2라는 정수 인덱스를 사용했기 때문이다.

반면 슬라이스를 쓰면 차원이 유지된다.

A[:, 2:3]

결과는 2차원 배열이다.

array([
    [12],
    [22],
    [32],
    [42]
])

비교:

A[:, 2]    # 1차원 결과
A[:, 2:3]  # 2차원 결과

이 차이는 머신러닝, 행렬 연산, 브로드캐스팅에서 중요하다.

15. 자주 쓰는 패턴 모음

전체 복사

b = a[:]

리스트의 얕은 복사.

처음 n개

a[:n]

마지막 n개

a[-n:]

n번째 이후 전부

a[n:]

마지막 원소 제외

a[:-1]

처음 원소 제외

a[1:]

뒤집기

a[::-1]

2칸씩 건너뛰기

a[::2]

역방향으로 2칸씩

a[::-2]

NumPy에서 모든 행의 특정 열

A[:, col]

NumPy에서 특정 행의 모든 열

A[row, :]

NumPy에서 일부 행과 일부 열

A[row_start:row_stop, col_start:col_stop]

16. 헷갈리기 쉬운 포인트

1. stop은 포함되지 않는다

a[1:4]

은 인덱스 1, 2, 3을 선택한다. 인덱스 4는 선택하지 않는다.

2. :만 쓰면 해당 축 전체다

a[:]      # 1차원 전체
A[:, 1]   # 모든 행의 1번 열
A[1, :]   # 1번 행의 모든 열

3. :는 와일드카드가 아니라 슬라이스 표기다

A[:, 1]에서 :가 와일드카드처럼 보이지만, 정확히는 해당 축 전체를 선택하는 슬라이스다.

:

은 내부적으로 다음과 같다.

slice(None, None, None)

4. a[:]는 리스트에서는 새 리스트를 만든다

b = a[:]

b는 새 리스트다. 하지만 얕은 복사라는 점에 주의해야 한다.

5. NumPy 슬라이스는 view일 수 있다

Python 리스트 슬라이스는 새 리스트를 만들지만, NumPy 배열 슬라이스는 보통 원본 데이터를 공유하는 view를 만든다.

A = np.array([10, 20, 30, 40])
B = A[1:3]

B[0] = 999

print(A)
# array([10, 999, 30, 40])

NumPy에서는 이 점이 매우 중요하다. 원본과 독립된 복사본이 필요하면 .copy()를 사용한다.

B = A[1:3].copy()

17. 내부 동작 관점

다음 코드는:

a[1:4]

대략 다음과 비슷하다.

a.__getitem__(slice(1, 4, None))

다음 코드는:

a[:]

대략 다음과 비슷하다.

a.__getitem__(slice(None, None, None))

NumPy의 다음 코드는:

A[:, 2]

대략 다음과 비슷하다.

A.__getitem__((slice(None, None, None), 2))

즉 NumPy는 여러 축의 인덱싱 정보를 튜플로 받는다.

A[:, 2]
   |  |
   |  +-- 두 번째 축: 정수 인덱스 2
   +----- 첫 번째 축: 전체 슬라이스

18. 기억할 핵심 문장

  1. :는 슬라이스 표기법이다.
  2. 슬라이스 표기법은 내부적으로 slice 객체가 된다.
  3. seq[start:stop:step]에서 stop은 포함되지 않는다.
  4. :만 쓰면 해당 축 전체를 뜻한다.
  5. ::start, stop, step을 모두 생략한 형태다.
  6. 리스트, 튜플, 문자열, NumPy 배열 모두 Python의 slice 개념을 사용한다.
  7. NumPy는 slice를 다차원 배열의 각 축에 적용할 수 있게 확장해서 쓴다.
  8. Python 리스트 슬라이스는 새 리스트를 만들지만, NumPy 슬라이스는 보통 view를 만든다.
  9. NumPy에서 정수 인덱스는 차원을 줄일 수 있고, 슬라이스는 차원을 유지한다.
  10. A[:, 2]는 “모든 행의 2번 열”이고, A[:, 2:3]은 “모든 행의 2번 열을 2차원 형태로 유지”한다.

19. 최종 요약표

표현 내부적 의미 설명
a[:] slice(None, None, None) 전체 선택
a[:3] slice(None, 3, None) 처음부터 3 전까지
a[2:] slice(2, None, None) 2부터 끝까지
a[1:4] slice(1, 4, None) 1부터 4 전까지
a[::2] slice(None, None, 2) 전체에서 2칸씩
a[::-1] slice(None, None, -1) 역순
A[:, 1] (slice(None, None, None), 1) 모든 행의 1번 열
A[1, :] (1, slice(None, None, None)) 1번 행의 모든 열
A[:3, 2] (slice(None, 3, None), 2) 처음 3개 행의 2번 열
A[:, 2:3] (slice(None, None, None), slice(2, 3, None)) 모든 행의 2번 열을 2차원으로 유지
반응형