IndexError: list index out of range 이해하기
Python 개발 시 자주 마주치는 IndexError: list index out of range는 시퀀스 자료형의 범위를 벗어난 위치에 접근할 때 발생합니다. 리스트의 인덱스는 0부터 시작하며, 마지막 요소의 인덱스는 len(리스트) - 1입니다.
오류 발생 예시
data = ['apple', 'banana', 'cherry']
print(data[5]) # IndexError 발생
위 코드에서 data는 3개 요소만 존재하므로 유효한 인덱스는 0, 1, 2입니다. 인덱스 5에 접근하면 예외가 발생합니다.
주요 발생 원인
1. 하드코딩된 인덱스 사용
코드에 고정된 숫자를 인덱스로 사용하면 데이터 변경 시 오류가 발생할 수 있습니다.
records = fetch_data() # 반환 데이터 수가 변동 가능
print(records[10]) # 데이터가 10개 미만이면 오류
2. 잘못된 음수 인덱스
음수 인덱스는 끝에서부터 역순으로 접근합니다. -len(리스트)보다 작은 값은 오류를 유발합니다.
items = [10, 20]
print(items[-5]) # -2까지만 유효, -5는 오류
3. 반복문에서의 범위 초과
while 루프나 잘못된 range 설정으로 인덱스가 벗어날 수 있습니다.
values = [1, 2, 3]
idx = 0
while idx <= len(values): # 등호로 인해 마지막에서 오류
print(values[idx])
idx += 1
4. 런타임 중 리스트 수정
순회 중 요소 삭제로 인해 인덱스가 무효화되는 경우입니다.
nums = [1, 2, 3, 4]
for i in range(len(nums)):
if nums[i] % 2 == 0:
del nums[i] # 삭제 후 인덱스 꼬임
방어적 코딩 기법
방법 1: 조건문으로 사전 검증
def fetch_element(container, position):
length = len(container)
if not container:
return None
if -length <= position < length:
return container[position]
return None
matrix = [[1, 2], [3, 4]]
result = fetch_element(matrix, 5) # 안전하게 None 반환
방법 2: 예외 처리 구조 활용
def process_items(data_source):
try:
target = data_source[2]['detail']
except IndexError:
target = "기본값"
except (KeyError, TypeError):
target = "구조 불일치"
return target
방법 3: 안전한 반복 패턴
인덱스 대신 직접 순회하거나 enumerate를 활용합니다.
# 권장 방식 1: 직접 순회
for entry in collection:
print(entry)
# 권장 방식 2: enumerate 활용
for offset, entry in enumerate(collection):
print(f"{offset}: {entry}")
# 권장 방식 3: zip으로 병렬 처리
names = ['kim', 'lee']
scores = [85, 92]
for n, s in zip(names, scores):
print(f"{n}: {s}")
방법 4: 슬라이싱으로 안전 접근
슬라이싱은 범위를 벗어나도 빈 결과를 반환하여 예외를 방지합니다.
sequence = [10, 20, 30]
# 안전한 부분 추출
subset = sequence[5:10] # 빈 리스트 [], 오류 없음
tail = sequence[-10:] # 전체 리스트 반환
방법 5: 수정 중인 컬렉션 처리
필터링 시 리스트 컴프리헨션이나 filter를 사용하여 원본을 직접 변경하지 않습니다.
raw_data = [1, 2, 3, 4, 5, 6]
# 안전한 필터링
evens = [x for x in raw_data if x % 2 == 0]
# 또는
from itertools import filterfalse
odds = list(filter(lambda x: x % 2, raw_data))
실전 적용 패턴
원형 큐 구현 시 인덱스 계산
class CircularBuffer:
def __init__(self, capacity):
self.capacity = capacity
self.storage = [None] * capacity
self.head = 0
self.tail = 0
def _normalize(self, index):
return index % self.capacity
def read(self, offset):
actual = self._normalize(self.head + offset)
if self.storage[actual] is None:
raise ValueError("빈 슬롯 접근")
return self.storage[actual]
다차원 배열 안전 접근
def safe_matrix_get(grid, row, col, default=None):
if not isinstance(grid, list) or not grid:
return default
if row < 0 or row >= len(grid):
return default
if col < 0 or col >= len(grid[row]):
return default
return grid[row][col]
board = [
['X', 'O', 'X'],
['O', 'X', 'O']
]
cell = safe_matrix_get(board, 5, 1, '?') # '?' 반환
디버깅 전략
| 상황 | 확인 사항 | 도구 |
|---|---|---|
| 예상치 못한 오류 | 변수 값, 리스트 길이 출력 | print(), logging |
| 복잡한 로직 | 조건부 중단점 설정 | pdb, IDE debugger |
| 정적 분석 | 잠재적 인덱스 문제 탐지 | mypy, pyright |
| 테스트 커버리지 | 경계값 테스트 | pytest, unittest |
요약
IndexError는 대부분 범위 검증 부재로 인해 발생합니다. 핵심 원칙은 다음과 같습니다:
- 인덱스 사용 전 길이 확인 또는 예외 처리
- 가능한 경우 인덱스 없는 순회 방식 채택
- 슬라이싱을 활용한 안전한 부분 접근
- 컬렉션 수정 시 별도 복사본 생성
- 정적 타입 검사 도구 도입