빈 제목과 한글 처리하기
60분 안팎
학습 목표
공백 제거와 문자열 길이의 의미를 확인합니다.
개념
왜 빈 제목을 일찍 거르는가
쪽수 10만 있고 책 제목이 없는 기록은 나중에 무엇을 읽었는지 알아보기 어렵습니다. 제목 칸에 공백 세 개를 넣으면 화면에서는 빈 제목처럼 보이지만 프로그램에는 길이가 있는 문자열이 들어옵니다. 입력을 받은 즉시 원문을 기준으로 판단하면 이런 기록을 통과시킬 수 있습니다. 이번에는 제목 앞뒤의 공백을 정리하고 정리된 값이 비었는지 판단합니다. 모든 공백을 지우는 대신 책 이름 안의 띄어쓰기는 보존합니다. 신입의 입력 처리에서 자주 보이는 문제는 검증 규칙보다 먼저 데이터를 과하게 바꾸는 일입니다.
원문과 정리된 제목을 나눈다
raw_title은 입력된 그대로의 문자열이고 title은 raw_title.strip()의 반환값입니다. strip()은 문자열 양끝의 공백 문자를 제거합니다. 보통의 스페이스뿐 아니라 탭과 개행 같은 공백 문자도 해당합니다. 문자열 가운데의 띄어쓰기나 탭을 모두 제거하는 함수는 아닙니다. title = raw_title.strip()처럼 결과를 이름에 연결해야 뒤의 코드가 정리된 값을 사용합니다. raw_title.strip()만 호출하고 원래 raw_title을 출력하면 앞뒤 공백이 그대로 남습니다. 문자열 메서드가 원문을 제자리에서 바꾼다고 가정하지 않습니다.
정리와 정책 변경은 다르다
제목 " 달빛 도서관 "을 "달빛 도서관"으로 만드는 것은 이번 입력 계약의 정리입니다. "달빛 도서관"을 "달빛도서관"으로 만드는 것은 내부 공백을 없애는 다른 정책입니다. replace(" ", "")를 쓰면 원래 제목까지 바뀝니다. 내부 공백이 두 개여도 이번에는 그대로 둡니다. 대문자와 소문자를 통일하거나 구두점을 지우는 정책도 추가하지 않습니다. 입력을 받아들이기 쉽게 하려는 선의로 규칙을 늘리면 나중에 사용자가 입력한 제목을 찾지 못할 수 있습니다. 정리 전후의 예시를 문서로 합의한 뒤 구현합니다.
빈 문자열을 비교로 확인한다
title == ""는 정리된 제목이 빈 문자열인지 묻습니다. ==는 두 값의 비교이며 =와 다릅니다. if title == "": 아래에 들여쓴 줄은 이 조건이 True일 때만 실행합니다. 조건 뒤에는 콜론을 붙이고 같은 분기 안의 줄은 같은 폭으로 들여씁니다. 여기서는 공백 네 칸을 사용합니다. 빈 제목이면 ERR_TITLE, 그렇지 않으면 OK|제목|길이를 출력하는 두 가지 경로를 만듭니다. 조건 구조를 먼저 좁게 연습하고 다음 레슨에서 여러 조건을 합칩니다. 제목을 정리하기 전에 raw_title == ""만 검사하면 공백만 있는 입력은 놓칩니다.
한글의 길이와 바이트 길이를 구분한다
len(title)은 Python 문자열의 Unicode 코드 포인트 개수를 셉니다. len(title.encode("utf-8"))는 UTF-8로 바꾼 바이트의 개수를 셉니다. "달빛 도서관"은 중간 공백까지 포함해 코드 포인트 6개이며 UTF-8에서는 16바이트입니다. 문자열 길이가 파일 크기나 터미널 표시 폭과 같다고 가정하면 안 됩니다. 이 레슨의 길이 출력은 정리된 문자열의 len 결과로 고정합니다. 화면에 보이는 한 글자가 언제나 코드 포인트 하나인 것도 아닙니다. 결합 문자나 이모지 조합의 표시 길이를 다룰 때는 별도 규칙이 필요합니다.
repr로 보이지 않는 차이를 드러낸다
print(raw_title)은 공백이나 탭의 존재를 알아보기 어려울 수 있습니다. repr(raw_title)을 출력하면 문자열 경계와 일부 제어 문자가 표현되어 원문을 조사하기 좋습니다. 따라하기에서는 repr 결과와 정리 후 결과를 비교합니다. repr은 사용자에게 보여 줄 정식 제목 형식이 아니라 개발 중 원문을 관찰하는 도구입니다. 제출 출력에는 이런 관찰 줄을 남기지 않습니다. 경계 공백을 지운 뒤에는 title을 출력하고 len(title)을 계산합니다. 정리된 제목을 출력하면서 길이는 raw_title로 세는 식으로 서로 다른 값을 섞지 않습니다.
한 줄 입력으로 빈 제목을 보존한다
이번 브라우저 실습은 제목 한 줄만 받습니다. 빈 제목 사례는 첫 줄이 바로 개행인 입력입니다. 공백 제목 사례는 공백을 넣은 뒤 개행합니다. input()은 개행을 제외한 원문을 읽으므로 두 경우를 모두 strip() 후 빈 문자열로 판단할 수 있습니다. 제목이 없는 것과 아예 입력 줄을 전달하지 않은 것은 다릅니다. 입력 줄 없이 실행하면 EOFError가 발생할 수 있습니다. 실습의 입력 계약은 제목 줄 한 개가 존재한다는 것이므로 입력창을 확인합니다. 미션에서는 제목을 명령행 인자로 전달할 때 빈 문자열도 하나의 인자로 유지하도록 따옴표를 사용합니다.
문법 오류와 데이터 오류를 나눈다
if title == "" 뒤의 콜론을 빠뜨리면 SyntaxError가 나며 들여쓰기가 어긋나면 IndentationError가 나올 수 있습니다. 이는 사용자가 빈 제목을 넣어서 생긴 업무 오류가 아니라 소스 문법 문제입니다. 코드의 표시된 줄과 그 직전 줄을 확인하고 콜론과 들여쓰기를 맞춥니다. 반대로 ERR_TITLE은 실행이 정상적으로 진행된 뒤 입력 규칙에 따라 정한 업무 결과입니다. 오류라는 단어가 들어 있다고 인터프리터 실패와 같은 것으로 보지 않습니다. 미션에서 업무 오류의 종료 코드는 문서로 정하고 검사하며, 레슨 채점에서는 정해진 결과 문자열을 비교합니다.
출력만 보지 말고 제목 보존도 확인한다
실습 starter는 공백 제거 결과를 버리고 원문을 출력합니다. 공백 없는 제목으로만 실행하면 정상처럼 보일 수 있습니다. 앞뒤 공백이 있는 한글 제목과 공백만 있는 제목을 함께 실행해 검증합니다. 내부에 공백 두 개가 있는 제목도 넣어 정리 범위를 넘어선 변경이 없는지 확인합니다. ASCII 문자와 한글이 섞인 제목은 코드 포인트 수와 바이트 수를 구분하는 좋은 사례입니다. 기대 결과를 먼저 적은 뒤 실행하면 구현에 맞춰 기대값을 바꾸는 실수를 줄일 수 있습니다. 0글자는 거부하고 1글자는 통과하는 경계도 확인합니다.
프로젝트의 기록 후보와 연결한다
다음 레슨에서는 정리된 제목과 쪽수 범위를 함께 검사합니다. 검증이 모두 성공하기 전에는 OK로 시작하는 기록 후보를 만들지 않습니다. 모듈 미션의 docs/input.md에는 앞뒤 공백을 없앤다는 규칙과 내부 공백을 보존한다는 규칙을 함께 씁니다. 제목이 비면 쪽수를 검사하기 전에 ERR_TITLE을 반환한다는 순서도 적습니다. 한 번의 정리로 모든 텍스트 문제를 해결했다고 선언하지 않고 이번 계약의 대상인 한 줄 제목을 정확히 다룹니다. 더 읽기에서는 문자열 표시와 인코딩을 넓혀 학습할 수 있습니다. 여기서 만든 작은 검증 규칙은 다음 모듈에서 여러 기록에도 재사용합니다.
따라하기
경계 공백 관찰
repr로 제목 양끝의 공백과 strip 적용 결과를 비교합니다.
raw_title = " 달빛 도서관 "
title = raw_title.strip()
print(repr(raw_title))
print(repr(title))
실행 결과
' 달빛 도서관 ' '달빛 도서관'
코드 포인트와 UTF-8 바이트 확인
두 결과가 무엇을 세는지 구분합니다.
title = "달빛 도서관"
print(len(title))
print(len(title.encode("utf-8")))
실행 결과
6 16
공백 제목 거부
제목 줄에 공백 세 개를 입력합니다. ERR_TITLE 뒤에 성공 결과가 나오지 않는지 확인합니다.
raw_title = input()
title = raw_title.strip()
if title == "":
print("ERR_TITLE")
else:
print(f"OK|{title}|{len(title)}")
실행 결과
ERR_TITLE
내부 공백 보존
메서드 호출만 한 결과와 반환값을 대입한 결과를 비교합니다.
raw_title = " 달빛 도서관 "
raw_title.strip()
print(repr(raw_title))
title = raw_title.strip()
print(repr(title))
print(len(title))
실행 결과
' 달빛 도서관 ' '달빛 도서관' 7
확인 문제
실습
제목 한 줄을 읽어 strip으로 앞뒤 공백만 제거합니다. 빈 제목이면 ERR_TITLE만 출력합니다. 나머지는 OK|정리된 제목|len 결과를 한 줄로 출력합니다. 내부 공백·한글·대소문자를 보존합니다. 길이는 바이트 수가 아닌 문자열의 코드 포인트 수입니다.
모범 답안
raw_title = input()
title = raw_title.strip()
if title == "":
print("ERR_TITLE")
else:
print(f"OK|{title}|{len(title)}")
더 읽기
면접 질문
- strip()의 반환값을 사용해야 하는 이유와 내부 공백 처리 범위를 설명해 주세요.
- 한글 제목의 len 결과와 UTF-8 바이트 수가 다른 이유는 무엇인가요?