Devin.KR

입력과 조건문으로 점검 데이터 읽기

90분 안팎

학습 목표

Python 문자열·리스트·조건문으로 점검 데이터를 처리합니다.

개념

목록을 판단 가능한 데이터로 만들기

점검 대상 목록을 사람이 눈으로 고르면 한 줄을 빠뜨리거나 비슷한 이름을 같은 것으로 오해할 수 있습니다. 이번 레슨에서는 입력 문자열을 목록으로 나누고 각 항목을 조건으로 분류합니다. 프로그램은 요청을 보내거나 주소를 찾아가지 않습니다. 입력을 읽고 허용된 문자열만 출력하는 도구입니다. 코드가 자동으로 승인권을 갖는 것이 아니라 사람이 정한 좁은 목록을 반복해서 적용하도록 만듭니다.

이 레슨의 입력 계약은 한 줄에 대상 식별자 하나입니다. 허용하는 식별자는 localhost와 127.0.0.1 두 개입니다. 이것들은 앞 레슨의 URL 전체와 다릅니다. 여기서는 조건문 학습을 위해 호스트 식별자만 분류하며 포트, 경로, 스킴이 붙은 문자열은 거절합니다. 브라우저 문제를 통과해도 실제 scope.json의 기간과 행위 검사를 대신할 수 없습니다.

문자열과 리스트

Python에서 문자열은 글자들을 담고, 리스트는 여러 값을 순서대로 담습니다. 표준 입력 전체는 sys.stdin.read()로 읽습니다. splitlines()는 줄 경계를 나누어 리스트를 만듭니다. 각 줄에 strip()을 적용하면 앞뒤 공백을 제거할 수 있습니다. 빈 줄은 처리를 건너뜁니다. 내용을 통째로 작은따옴표로 감싸서 입력하면 그 따옴표도 대상 이름의 일부이므로 일치하지 않습니다.

줄을 나누는 규칙은 업무 계약의 일부입니다. split()으로 모든 공백을 나누면 “localhost extra”라는 잘못된 한 줄에서 localhost만 뽑아 통과시킬 수 있습니다. 여기서는 한 줄 전체를 하나의 후보로 봅니다. 앞뒤 공백은 정리하되 줄 안의 공백, 포트, 접미사는 없애지 않습니다. 보기 좋게 정리한다는 이유로 의미를 바꾸면 검사가 의도보다 넓어집니다.

리스트를 만드는 코드에서 lines라는 이름은 여러 줄이라는 뜻을 드러냅니다. 반복 중 line은 현재 한 줄이고 target은 공백 정리가 끝난 값입니다. 이름을 구분하면 어떤 값을 비교해야 하는지 추적하기 쉽습니다. 원본 줄을 곧바로 출력하는 대신 검사를 통과한 target만 결과 목록에 넣습니다. 입력 자료와 출력 자료의 역할을 섞지 않는 작은 연습입니다.

조건문으로 좁은 허용 목록 적용하기

허용 목록은 allowed = ["localhost", "127.0.0.1"]로 표현합니다. target in allowed는 전체 문자열이 목록 원소 하나와 같은지 확인합니다. 참이면 accepted.append(target)으로 결과에 추가합니다. 거짓이면 결과에 넣지 않습니다. 이번 분류는 대소문자를 바꾸지 않으므로 LOCALHOST는 허용되지 않습니다. 실제 DNS 이름의 동작을 일반화한 규칙이 아니라 이 문제의 문자 비교 계약입니다.

if target == "localhost" or "127.0.0.1"라고 쓰면 기대와 다른 결과가 나옵니다. 뒤의 문자열 자체가 비어 있지 않아 참으로 취급되기 때문입니다. 비교식을 양쪽에 온전히 쓰거나 허용 목록 포함 여부를 사용합니다. 오류 메시지가 없어도 분류는 틀릴 수 있으므로 외부 식별자가 섞인 입력으로 결과를 확인합니다. 조건문의 정확성은 정상 사례만으로는 판단하기 어렵습니다.

for 문은 줄을 하나씩 꺼내 검사합니다. 들여쓰기된 문장은 그 반복 또는 조건에 속합니다. 빈 줄이면 continue로 이번 항목만 건너뜁니다. break를 쓰면 그 뒤의 모든 입력을 읽었어도 분류하지 않게 됩니다. 빈 줄 다음에 허용 대상이 오는 테스트로 두 동작의 차이를 확인할 수 있습니다. 반복 횟수보다 어떤 항목을 처리하고 남겼는지에 집중합니다.

정렬과 중복의 의미

결과는 sorted(accepted)로 문자열 오름차순 정렬합니다. 숫자 형태인 127.0.0.1이 localhost보다 먼저 나옵니다. 정렬은 입력 순서가 달라도 같은 목록 비교가 가능하도록 합니다. IP 주소를 수치로 정렬하는 기능은 아닙니다. 이번 허용 집합은 두 문자열뿐이므로 복잡한 주소 해석기가 필요하지 않습니다. 문자열 비교를 네트워크 연결 가능성 판정으로 확대하지 않습니다.

중복은 그대로 보존합니다. localhost가 두 줄 들어오면 두 줄 출력합니다. 이 문제는 고유 대상 수를 구하는 문제가 아니라 통과한 입력 건을 분류하는 문제입니다. set으로 바꾸면 같은 대상을 여러 번 입력했다는 정보가 사라집니다. 나중에 중복 제거가 필요한 업무에서는 그 요구를 명시적으로 추가합니다. 현재 출력 형식에서는 먼저 통과 건수를 쓰고 다음 줄부터 정렬된 식별자를 씁니다.

허용 대상이 없으면 0만 출력하고 끝냅니다. 빈 입력, 빈 줄만 있는 입력, 외부 문자열만 있는 입력 모두 같은 형식입니다. 건수는 정수이므로 0.0을 쓰지 않습니다. print(len(result)) 다음에 반복 출력하면 마지막 결과 뒤에는 줄바꿈이 생깁니다. 디버깅 설명이나 입력 안내 문구를 출력하면 채점 결과 문자열이 달라지므로 출력 계약에 포함하지 않습니다.

코드를 완성하는 순서

먼저 표준 입력을 읽고 줄을 분리하는 두 줄을 실행합니다. 다음에 공백과 빈 줄 처리를 더합니다. 이어서 목록 포함 조건을 적용하고 마지막에 정렬과 출력 단계를 붙입니다. 한꺼번에 모든 코드를 붙여 넣기보다 각 중간값이 어떤 자료형인지 적어 보면 문제 위치를 좁힐 수 있습니다. 따라하기는 입력 계약을 작은 사례로 나누어 보여 줍니다.

브라우저 starter는 입력과 허용 목록을 준비해 두지만 결과를 비워 둡니다. for 문 안에서 target을 만들고, 빈 줄을 넘기고, 허용 원소를 accepted에 추가합니다. sorted 결과로 건수와 줄별 항목을 출력합니다. tests에는 정상 혼합, 빈 입력, 접두사 위장, 중복, 공백, 대소문자와 내부 공백 사례가 있습니다. 각 사례를 고치기 전에 어떤 조건에서 통과해야 하는지 말로 설명합니다.

실패 결과를 읽기

IndentationError는 들여쓰기가 문장 구조에 맞지 않는다는 뜻입니다. if나 for 다음 줄이 한 단계 들어갔는지, 같은 블록의 줄들이 같은 폭인지 봅니다. NameError는 이름이 정의되기 전에 사용되거나 철자가 달라졌을 때 발생합니다. accepted와 accept 같은 작은 차이를 확인합니다. SyntaxError가 콜론 근처를 가리키면 조건문 끝의 콜론이나 문자열 따옴표를 점검합니다.

실행이 성공했는데 기대보다 건수가 크면 허용 조건과 빈 줄 처리를 살핍니다. 건수는 맞고 순서가 다르면 정렬된 결과를 실제 출력에 썼는지 봅니다. 중복 테스트만 실패하면 set으로 바꾸거나 같은 값을 제거한 부분을 확인합니다. 실패를 입력 문제라고 단정하지 말고 해당 입력을 종이에 줄별로 분류한 결과와 코드의 결과를 비교합니다.

판단의 한계 기록하기

이 함수는 두 식별자와 정확히 같은 입력을 추렸다는 것만 보여 줍니다. 다른 루프백 표현, 호스트 별칭, 실제 이름 해석 결과는 처리하지 않습니다. 비슷한 문자열을 거절해도 실제 외부 연결을 막는 방화벽은 아닙니다. 출력 목록을 요청 도구에 자동 전달하는 기능도 없습니다. 분류기를 설명할 때 무엇을 검사했는지 구체적으로 말하면 다음 모듈에서 통신과 권한 검사를 추가할 지점을 알 수 있습니다.

완성 후에는 허용 식별자를 한 줄 더 추가해야 하는 상황을 생각합니다. 조건문 여러 곳을 수정하기보다 목록 한 곳에 정책을 두면 누락 가능성을 줄일 수 있습니다. 다만 목록에 추가할 권한은 이 코드에 없습니다. 승인된 계약이 변경된 뒤 그 내용을 코드와 테스트에 함께 반영합니다. 프로그래밍의 편리함과 점검 권한의 출처를 구분하는 것이 이 레슨의 마지막 목표입니다.

문법 근거는 Python 제어 흐름 문서와 sorted 함수 문서에서 확인할 수 있습니다.

따라하기

줄과 내부 공백 구분

lines = " localhost \nlocalhost extra\n\n".splitlines()
for line in lines:
    print(repr(line.strip()))

실행 결과

'localhost'
'localhost extra'
''

포함 조건 확인

allowed = ["localhost", "127.0.0.1"]
for target in ["localhost", "localhost.evil", "LOCALHOST"]:
    print(target, target in allowed)

실행 결과

localhost True
localhost.evil False
LOCALHOST False

정렬된 출력 생성

표준 입력으로 localhost, example.com, 127.0.0.1, localhost를 각각 한 줄에 넣습니다. 브라우저 실행기 입력란에 네 줄을 붙여 넣고 실행합니다.

import sys
allowed = ["localhost", "127.0.0.1"]
accepted = []
for line in sys.stdin.read().splitlines():
    target = line.strip()
    if not target:
        continue
    if target in allowed:
        accepted.append(target)
result = sorted(accepted)
print(len(result))
for target in result:
    print(target)

실행 결과

3
127.0.0.1
localhost
localhost

빈 입력도 같은 형식

import sys
allowed = ["localhost", "127.0.0.1"]
accepted = []
for line in sys.stdin.read().splitlines():
    target = line.strip()
    if not target:
        continue
    if target in allowed:
        accepted.append(target)
result = sorted(accepted)
print(len(result))
for target in result:
    print(target)

실행 결과

0

확인 문제

실습

한 줄 한 대상 식별자를 읽습니다. 앞뒤 공백을 없애고 빈 줄을 건너뜁니다. localhost와 127.0.0.1에 정확히 일치하는 줄만 모아 문자열 오름차순으로 출력합니다. 중복은 유지합니다. 첫 줄은 통과 건수, 이후는 한 줄 한 식별자입니다. 빈 입력은 0 한 줄입니다. 실제 주소 검사나 네트워크 요청은 하지 않습니다.

모범 답안
import sys
allowed = ["localhost", "127.0.0.1"]
accepted = []
for line in sys.stdin.read().splitlines():
    target = line.strip()
    if not target:
        continue
    if target in allowed:
        accepted.append(target)
result = sorted(accepted)
print(len(result))
for target in result:
    print(target)

더 읽기

면접 질문

  • 허용 목록을 코드로 적용할 때 입력 계약과 경계 사례를 어떻게 정하나요?