Published on

파이썬 문자열 찾기에서 find와 index의 결정적 차이 예외 처리까지 실무 패턴

Authors

서론

로그 파싱, URL 처리, 사용자 입력 검증처럼 문자열에서 특정 토큰을 찾는 작업은 파이썬에서 정말 자주 등장합니다. 그런데 str.find()str.index()는 겉보기엔 비슷해도 실패했을 때의 동작이 완전히 달라서, 사소한 선택이 장애나 미묘한 버그로 이어질 수 있습니다.

  • find()는 못 찾으면 -1을 돌려주고
  • index()는 못 찾으면 ValueError 예외를 던집니다.

이 차이를 제대로 이해하면, “없어도 정상인 상황”과 “없으면 비정상인 상황”을 코드 구조로 명확히 표현할 수 있고, 예외 처리 비용/가독성까지 함께 최적화할 수 있습니다.

문자열 검색 전반을 더 넓게 정리한 글이 필요하면: 파이썬으로 문자열 찾기 완전 정복 find와 count로 검색과 집계를 빠르게


find는 -1 반환, index는 ValueError 예외

기본 동작 비교

text = "hello world"

print(text.find("world"))   # 6
print(text.find("python"))  # -1

print(text.index("world"))  # 6
print(text.index("python")) # ValueError: substring not found

정리하면:

  • find(sub[, start[, end]]) -> int
    • 찾으면 시작 인덱스
    • 못 찾으면 -1
  • index(sub[, start[, end]]) -> int
    • 찾으면 시작 인덱스
    • 못 찾으면 ValueError

둘 다 start, end 슬라이스 범위를 지원합니다.


언제 find를 쓰고, 언제 index를 써야 하나

1) “없을 수도 있다”면 find가 자연스럽다

예: 옵션 파라미터가 있을 수도/없을 수도 있는 URL 처리

url = "https://example.com/search?q=python"

pos = url.find("?")
if pos == -1:
    path = url
    query = ""
else:
    path = url[:pos]
    query = url[pos+1:]

print(path, query)
  • ?가 없는 URL도 정상 입력인 경우가 많습니다.
  • 이런 상황에서 index()를 쓰면 예외 처리 블록이 필요해지고, 코드가 오히려 장황해질 수 있습니다.

2) “반드시 있어야 한다”면 index로 의도를 강제한다

예: 로그 포맷이 고정이고, 구분자가 없으면 데이터가 깨진 것

line = "2026-02-22|INFO|service started"

# '|'는 반드시 있어야 한다는 전제
first_sep = line.index("|")
print(line[:first_sep])  # 날짜
  • 구분자가 없다면 조용히 넘어가는 게 아니라 즉시 실패해야 문제를 빨리 발견합니다.
  • 이럴 때 index()는 “없으면 비정상”이라는 계약을 코드로 표현합니다.

실무에서 가장 흔한 버그: find 결과를 truthy로 검사하기

find()의 반환값은 인덱스(정수)입니다. 파이썬에서 0은 False로 평가되기 때문에, 아래 코드는 자주 사고를 냅니다.

잘못된 코드

s = "error: something"

if s.find("error"):
    print("에러 포함")
else:
    print("에러 없음")

"error"는 맨 앞(인덱스 0)에 있으므로 s.find("error")0을 반환합니다. 0은 False이므로 결과는 **"에러 없음"**이 됩니다.

올바른 코드

if s.find("error") != -1:
    print("에러 포함")

또는 더 파이썬다운 방식으로는 in을 쓰는 편이 안전합니다.

if "error" in s:
    print("에러 포함")

Best Practice: 존재 여부만 필요하면 in, 위치가 필요하면 find/index

존재 여부만 필요할 때

if "token=" in header:
    ...
  • 가독성이 좋고
  • 실수(0/False 문제)를 피하고
  • 의도가 명확합니다.

위치(인덱스)가 필요할 때

  • “없어도 정상”이면 find() + -1 처리
  • “없으면 오류”이면 index() + 예외로 빠르게 실패

예외 처리 패턴: index를 안전하게 쓰는 방법

index()를 쓰되, 입력이 외부(사용자 입력/외부 API)에서 오면 예외를 잡아 에러 메시지에 맥락을 남기는 것이 중요합니다.

def parse_kv(line: str) -> tuple[str, str]:
    # "key=value" 형태를 기대
    try:
        i = line.index("=")
    except ValueError as e:
        raise ValueError(f"Invalid format, expected key=value: {line!r}") from e

    key = line[:i].strip()
    value = line[i+1:].strip()
    return key, value

print(parse_kv("mode=prod"))
# print(parse_kv("mode"))  # ValueError: Invalid format...

포인트:

  • 원래 예외(from e)를 연결해 디버깅 단서를 보존
  • !r로 원문을 repr 형태로 남겨 공백/이스케이프를 포함한 정확한 입력 확인

문자열에 보이지 않는 문자(개행/탭)가 섞여 파싱이 실패하는 경우도 많습니다. 이럴 때 repr 출력과 함께 이스케이프 문자 이해가 도움이 됩니다.

관련 참고: 파이썬 이스케이프 문자 정리 줄바꿈과 탭 문자열 처리 실전 가이드


start/end 파라미터로 “두 번째 등장” 찾기

find/index는 범위를 지정할 수 있어, 특정 구간 이후의 검색에 유용합니다.

s = "path/to/file.txt"

first = s.find("/")
second = s.find("/", first + 1)

print(first, second)  # 4 7

index도 동일하지만, 두 번째 /가 없을 가능성이 있으면 find가 더 편합니다.


트러블슈팅 체크리스트

1) 못 찾았는데 예외가 난다

  • index()를 쓰고 있는지 확인
  • 외부 입력이면 try/except ValueError로 감싸고, 입력 원문을 !r로 로깅

2) 분명히 포함되어 있는데 조건문이 안 탄다

  • find() 결과를 if s.find(x):처럼 검사하지 않았는지 확인
  • != -1로 비교하거나 in 사용

3) 대소문자 때문에 못 찾는다

hay = "Error: Disk Full"
needle = "error"

print(hay.lower().find(needle))  # 0
  • 단, lower()/casefold()는 새로운 문자열을 만듭니다. 큰 텍스트에서 반복 호출한다면 한 번만 변환해 재사용하세요.

결론

  • find()는 실패 시 -1을 반환: 없어도 정상인 케이스에 적합
  • index()는 실패 시 ValueError: 없으면 비정상인 케이스를 강제하고 조기 실패에 유리
  • find() 결과를 truthy로 검사하는 실수(0 문제)를 피하려면 != -1 또는 in을 사용
  • 외부 입력 파싱에서는 index() + 예외 처리로 맥락 있는 오류 메시지를 남기는 패턴이 실무에서 강력합니다.