학습 자료

문자열을 자른다 — 슬라이싱과 반복문


Article

AI 서비스를 만든다고 하면 보통 모델부터 떠올린다. 그런데 실제로 문서를 다루는 AI 서비스에서 가장 먼저 하는 일은 모델과 아무 상관이 없다. 긴 문서를 짧은 조각으로 자르는 것이다.

이번 강의에서는 그 자르는 일을 파이썬으로 직접 해본다. 라이브러리를 하나도 안 쓰고, 설치할 것도 없다. 파이썬만 있으면 된다.

왜 자르나

모델에게 문서를 통째로 넘길 수는 없다. 넣을 수 있는 글자 수에 한계가 있기 때문이다. 그리고 한계가 없다 해도 통째로 넣으면 안 된다. 질문이 "연차 이월"인데 회사 규정 50장을 전부 넘기면, 모델은 그 안에서 답을 찾느라 헤매고 비용도 50장어치가 나간다.

문서 하나가 답이 되기까지
  1. 긴 문서규정집 · 매뉴얼 · 가이드
  2. 자른다이번 강의
  3. 숫자로 바꾼다임베딩
  4. 질문과 가까운 조각을 찾는다유사도
  5. 그 조각만 모델에게 넘긴다답변 생성

첫 칸이 무너지면 나머지가 다 무너진다. 조각이 엉뚱하게 잘리면 그 뒤로 아무리 좋은 모델을 써도 답이 안 나온다.

파이썬 켜기

터미널에 이렇게 친다.

python

Windows에서 python이 안 먹으면 py를 친다. 이렇게 나오면 성공이다.

터미널
  • Python 3.11.15 (main, ...) [MSC v.1937 64 bit (AMD64)] on win32
  • Type "help", "copyright", "credits" or "license" for more information.
  • >>>

>>> 가 보이면 파이썬이 한 줄씩 받아먹을 준비가 된 상태다. 여기서는 코드를 한 줄 치면 결과가 바로 나온다. 이번 강의는 이 화면에서 한다.

문자열은 글자를 한 줄로 세운 것이다

문자열을 하나 만든다. 아래를 그대로 복사해 붙여넣는다.

# 이 시리즈에서 계속 쓸 예제 문서. IT 직무 가이드를 짧게 줄인 것이다
text = "백엔드 개발자는 서버와 데이터베이스를 다룬다. 사용자 눈에 보이지 않는 곳에서 데이터를 저장하고 꺼내는 일을 한다. 주로 쓰는 언어는 자바, 파이썬, 노드다. 요즘은 클라우드 지식도 함께 요구된다."

len(text)
터미널
  • 110

len() 은 길이를 재는 함수다. 이 문서는 110글자다. 파이썬에서 한글 한 글자는 그냥 한 글자로 센다. 영어든 한글이든 공백이든 똑같이 1이다.

문자열은 글자를 한 줄로 세워놓은 것이고, 각 자리에 번호가 붙어 있다. 그 번호로 글자 하나를 꺼낼 수 있다.

text[0]     # 첫 글자
터미널
  • '백'

번호는 0부터 시작한다. 첫 글자가 1번이 아니라 0번이다. 프로그래밍을 처음 하면 여기서 한 번씩 걸린다.

번호는 0부터

text = "백엔드 개발자는 ..."

text[0]
'백'
text[1]
'엔'
text[2]
'드'
text[109]
'.' — 마지막 글자
길이가 110이니 마지막 번호는 109다

마지막 글자를 꺼내려면 109를 세고 있을 필요가 없다. 음수를 쓰면 뒤에서부터 센다.

text[-1]    # 뒤에서 첫 번째
터미널
  • '.'

슬라이싱 — 여러 글자를 한 번에 꺼낸다

글자 하나가 아니라 구간을 꺼내는 것을 슬라이싱이라고 한다. 대괄호 안에 콜론을 넣고 시작과 끝을 적는다.

text[0:20]      # 0번부터 20번 「앞」까지
터미널
  • '백엔드 개발자는 서버와 데이터베이스를'

세어보면 정확히 20글자다. 여기서 중요한 규칙이 하나 있다.

앞뒤를 비워두면 「처음부터」와 「끝까지」가 된다.

text[:20]       # 처음부터 20번 앞까지 — 위와 같다
text[90:]       # 90번부터 끝까지
터미널
  • '백엔드 개발자는 서버와 데이터베이스를'
  • '즘은 클라우드 지식도 함께 요구된다.'

끝 번호가 길이를 넘어가도 에러가 안 난다. 있는 만큼만 준다. 이건 나중에 문서를 자를 때 마지막 조각을 따로 처리하지 않아도 되게 해준다.

text[90:9999]   # 9999번 같은 건 없는데도 에러가 아니다
터미널
  • '즘은 클라우드 지식도 함께 요구된다.'

문서를 30글자씩 자른다

이제 본론이다. 110글자짜리 문서를 30글자씩 잘라보자. 손으로 하면 이렇게 된다.

text[0:30]
text[30:60]
text[60:90]
text[90:120]

네 줄을 치면 네 조각이 나온다. 그런데 문서가 5,000글자면 이 짓을 167번 해야 한다. 그래서 반복문을 쓴다.

# range(시작, 끝, 건너뛰기) — 0, 30, 60, 90 을 차례로 만든다
for start in range(0, len(text), 30):
    print(text[start:start + 30])
터미널
  • 백엔드 개발자는 서버와 데이터베이스를 다룬다. 사용자
  • 눈에 보이지 않는 곳에서 데이터를 저장하고 꺼내는 일을
  • 한다. 주로 쓰는 언어는 자바, 파이썬, 노드다. 요
  • 즘은 클라우드 지식도 함께 요구된다.

네 조각이 나왔다. range(0, 110, 30) 이 0, 30, 60, 90 네 개를 만들었고, 마지막 text[90:120] 은 110에서 끊겨 20글자만 나왔다.

코드를 한 줄씩 뜯어보면 이렇다.

반복문이 하는 일
  1. 01

    range(0, len(text), 30) 이 시작 번호를 만든다

    0에서 시작해 30씩 더하면서 110을 넘기 전까지. 0, 30, 60, 90 네 개.

  2. 02

    start 라는 이름에 그 값이 하나씩 들어간다

    첫 바퀴에 0, 둘째 바퀴에 30. 이름은 아무거나 써도 되는데 뜻이 보이는 이름이 낫다.

  3. 03

    text[start:start + 30] 으로 30글자를 꺼낸다

    start 가 30이면 text[30:60] 이 된다. 앞 조각의 끝과 다음 조각의 시작이 같은 숫자라 겹치지 않는다.

  4. 04

    print 를 안 쓰면 for 문 안에서는 화면에 안 나온다. 한 줄짜리 명령일 때만 자동으로 보여준다.

그런데 문제가 보인다

출력을 다시 보자. 첫 조각이 이렇게 끝난다.

조각 경계에서 무슨 일이 일어났나

1번 조각 끝

text[0:30]

  • ... 다룬다. 사용자
  • 「사용자」 뒤에서 뚝 끊겼다
  • 문장이 안 끝났다

2번 조각 시작

text[30:60]

  • 눈에 보이지 않는 곳에서 ...
  • 앞 조각과 이어야 뜻이 산다
  • 「사용자 눈에」가 두 조각에 걸쳤다

「사용자 눈에 보이지 않는 곳」이라는 한 덩어리가 두 조각으로 찢어졌다. 이 상태로 검색을 하면 어떻게 될까. 누가 "사용자 눈에 안 보이는 일이 뭔가요"라고 물으면, 답이 두 조각에 나뉘어 있어서 어느 쪽도 온전한 답이 못 된다.

숫자로만 자르면 반드시 이 일이 생긴다. 글자 수는 문장의 사정을 모르기 때문이다.

문단으로 자르기 — split()

문장 끝에 마침표가 있으니, 마침표를 기준으로 자르면 문장이 안 찢어진다. split() 이 그 일을 한다.

# 마침표 뒤 공백을 기준으로 쪼갠다
parts = text.split(". ")
parts
터미널
  • ['백엔드 개발자는 서버와 데이터베이스를 다룬다',
  • '사용자 눈에 보이지 않는 곳에서 데이터를 저장하고 꺼내는 일을 한다',
  • '주로 쓰는 언어는 자바, 파이썬, 노드다',
  • '요즘은 클라우드 지식도 함께 요구된다.']

대괄호로 감싸인 이것이 리스트다. 문자열 여러 개를 순서대로 담아둔 상자다. 이번엔 문장이 하나도 안 찢어졌다.

len(parts)      # 몇 조각인가
parts[0]        # 첫 조각 — 리스트도 0번부터다
터미널
  • 4
  • '백엔드 개발자는 서버와 데이터베이스를 다룬다'

리스트도 문자열처럼 0번부터 번호가 붙고, 슬라이싱도 똑같이 된다. 문자열은 글자를 담은 줄이고 리스트는 아무거나 담은 줄인데, 다루는 방식이 같다.

parts[:2]       # 앞의 두 조각만
터미널
  • ['백엔드 개발자는 서버와 데이터베이스를 다룬다', '사용자 눈에 보이지 않는 곳에서 데이터를 저장하고 꺼내는 일을 한다']

조각에 번호를 붙여 보기 좋게 찍는다

조각이 여러 개면 몇 번째인지 알아야 한다. enumerate() 를 쓰면 번호와 값을 같이 준다.

for i, part in enumerate(parts):
    print(i, part)
터미널
  • 0 백엔드 개발자는 서버와 데이터베이스를 다룬다
  • 1 사용자 눈에 보이지 않는 곳에서 데이터를 저장하고 꺼내는 일을 한다
  • 2 주로 쓰는 언어는 자바, 파이썬, 노드다
  • 3 요즘은 클라우드 지식도 함께 요구된다.

번호가 0부터 붙었다. 사람이 볼 화면이니 1번부터 세고, 길이도 같이 보고 싶다. 이럴 때 f-string 을 쓴다.

# 따옴표 앞에 f 를 붙이면 중괄호 안이 값으로 바뀐다
for i, part in enumerate(parts):
    print(f"{i + 1}번 조각 ({len(part)}글자) : {part}")
터미널
  • 1번 조각 (24글자) : 백엔드 개발자는 서버와 데이터베이스를 다룬다
  • 2번 조각 (37글자) : 사용자 눈에 보이지 않는 곳에서 데이터를 저장하고 꺼내는 일을 한다
  • 3번 조각 (22글자) : 주로 쓰는 언어는 자바, 파이썬, 노드다
  • 4번 조각 (21글자) : 요즘은 클라우드 지식도 함께 요구된다.

f-string 은 문자열 안에서 계산까지 된다. {i + 1} 처럼 식을 그냥 넣으면 결과가 들어간다.

이번 강의에 나온 것
쓴 것하는 일
len(x)길이를 잰다len(text) → 110
text[3]3번 글자 하나'개'
text[-1]뒤에서 첫 글자'.'
text[0:20]0번부터 19번까지20글자
text[:20]처음부터위와 같다
text[90:]끝까지나머지 전부
range(0, n, 30)0, 30, 60 ... 을 만든다반복문의 재료
text.split(". ")기준 문자로 쪼개 리스트로문장 4개
enumerate(리스트)번호와 값을 같이 준다for i, x in ...
f"{값}"문자열 안에 값을 넣는다f"{n}번"

스스로 해보기

붙여넣지 말고 직접 쳐본다. 5분이면 된다.

확인 문제 셋
  1. 01

    text 의 가운데 20글자를 꺼내본다

    110글자니까 가운데는 45번쯤이다. 숫자를 바꿔가며 원하는 대목이 나올 때까지 해본다.

  2. 02

    30이 아니라 50글자씩 잘라본다

    조각 수가 어떻게 달라지나. 문장이 찢어지는 자리도 달라졌을 것이다.

  3. 03

    조각 중에 가장 긴 것이 몇 번인지 눈으로 찾아본다

    f-string 으로 길이를 찍어놓고 보면 금방 보인다. 다음 강의에서 이걸 코드로 찾는 방법을 배운다.

정리하면

문자열은 번호가 붙은 글자의 줄이고, 슬라이싱으로 구간을 꺼낸다. 반복문과 합치면 긴 문서를 일정한 크기로 자를 수 있다. 다만 글자 수로만 자르면 문장이 찢어지고, 그래서 문단 단위 자르기와 오버랩이 필요해진다.

이 문제를 눈으로 확인한 게 이번 강의의 핵심이다. 다음 강의에서는 조각을 담아둔 리스트를 함수로 다루고, 점수를 매겨 상위 3개만 뽑는 법을 배운다. 검색의 마지막 단계다.

문자열을 자른다 — 슬라이싱과 반복문 — 디코드랩(DCODELAB)