Devin.KR

변수와 조건으로 행 검사

75분 안팎

학습 목표

표준 입력 JSON의 통행량을 숫자로 검증하고 오류 행 번호를 출력합니다.

개념

문자열을 숫자로 보이게 두지 않습니다

통행량이 화면에 100으로 보인다고 숫자라고 단정할 수 없습니다. CSV의 일반 DictReader는 값을 문자열로 읽고 JSON은 숫자·문자열·불린·null을 구분합니다. 이번 브라우저 계약은 문자열 또는 JSON 정수의 통행량을 허용합니다. 정제 함수의 책임은 허용한 입력을 정수로 바꾸고, 허용하지 않은 입력을 행 번호로 찾아내는 것입니다. 변환 규칙을 먼저 적으면 매 실행마다 판단이 달라지는 일을 줄입니다.

변수는 값을 가리키는 이름입니다. raw에는 받은 값을, text에는 앞뒤 공백을 정리한 문자열을, vehicles에는 검증된 정수를 붙입니다. 같은 변수에 여러 의미를 덮어쓰면 원문을 잃어 오류 설명이 어려워집니다. 조건문 if는 조건이 참일 때 들여쓴 블록을 실행하고 else는 그렇지 않을 때 실행합니다. 비교 결과를 이름 붙여 저장하면 어떤 조건이 행을 거부했는지 읽기 쉽습니다.

허용 형식과 범위는 따로 검사합니다

이번 통행량은 대 단위 정수이고 음수는 허용하지 않습니다. 앞뒤 공백은 제거하지만 내부 쉼표나 소수점은 자동으로 지우지 않습니다. 문자열의 모든 문자가 ASCII 숫자인지 정규식 [0-9]+로 검사한 뒤 int로 변환합니다. 빈 문자열은 형식에 맞지 않습니다. 1.5를 int(float(...))로 바꾸면 측정값을 잘라 버리므로 정제의 근거가 될 수 없습니다. 001은 정수 1로 정규화하는 것을 이 계약에서 허용합니다.

JSON 불린 true는 Python의 True가 됩니다. Python에서는 bool이 int의 하위 타입이므로 isinstance(value,int)만 검사하면 True를 1대로 받아들이기 쉽습니다. 이 실습에서는 type(value) is int로 정확한 정수 타입을 확인합니다. JSON 실수 3.0은 개수가 정수처럼 보여도 계약상 거부합니다. 외부 계약이 달라지면 이 선택을 문서와 테스트에서 함께 바꿉니다.

결측과 오류를 같은 0으로 숨기지 않습니다

이번 브라우저 실습은 빈 통행량도 오류 번호로 반환하여 숫자 계약을 연습합니다. 모듈 미션에서는 앞 단계의 정상 결측을 별도 규칙으로 보존합니다. 따라서 브라우저 함수의 거부 정책을 그대로 모든 CSV에 적용하지 않습니다. null과 빈 문자열을 0으로 대체하면 없던 관측이 생깁니다. 값 검증이란 편리한 숫자를 만들어 내는 것이 아니라 입력 상태를 정확히 분류하는 일입니다.

if not vehicles는 0과 None을 모두 참으로 판정하므로 결측 검사로 부적절합니다. 결측은 vehicles is None, 범위는 vehicles < 0처럼 목적별 조건을 둡니다. and는 앞 조건이 거짓이면 뒤 조건을 실행하지 않는 단락 평가를 합니다. 먼저 타입과 형식을 확인하고 나중에 숫자 비교를 하면 문자열과 정수의 잘못된 비교를 피할 수 있습니다. 조건을 한 줄로 과도하게 묶기보다 순서가 보이게 작성합니다.

행 번호로 다시 원문을 찾습니다

rows는 입력 JSON의 목록이며 enumerate(rows,1)은 원소와 1부터 시작하는 위치를 같이 줍니다. 사람이 보는 레코드 번호를 반환하기 위해 시작 값을 1로 지정합니다. CSV의 헤더와 물리적 줄 번호는 포함하지 않는다는 규칙을 미션에서도 유지합니다. 여러 줄로 작성된 따옴표 CSV 레코드는 물리적 줄 수와 다를 수 있으므로 번호의 의미를 계약에 적습니다.

errors.append(number)는 오류 목록 뒤에 번호를 추가합니다. 모든 행이 정상이면 빈 목록을 출력합니다. json.dumps는 Python 목록을 JSON 문자열로 바꾸고 print는 한 줄을 출력합니다. 표준 입력이 잘못된 JSON이면 JSONDecodeError에서 line과 column을 확인합니다. 이번 채점은 JSON 목록 자체는 유효하다고 가정하므로 문법 오류와 통행량 오류를 구분합니다.

오류 메시지로 검사 지점을 좁힙니다

TypeError에서 str과 int가 함께 보이면 변환 전 값을 수치 계산에 사용했는지 확인합니다. ValueError의 invalid literal for int()는 형식이 정수 해석에 맞지 않는다는 뜻입니다. try와 except를 사용할 때는 예상한 ValueError만 처리합니다. except Exception으로 전체 코드를 둘러싸고 0을 반환하면 프로그래밍 오류까지 정상 관측으로 바뀝니다. 미션의 rejected.csv에는 원문과 이유를 남겨 사람이 확인하도록 합니다.

정상 값 0, 앞뒤 공백, 음수 문자열, 소수, 불린, null을 테스트에 넣습니다. 가장 작은 입력인 빈 목록에서도 정해진 JSON 출력이 나와야 합니다. 오답 번호를 입력 순서로 출력하면 동료가 원문과 빠르게 대조할 수 있습니다. 이 함수는 품질 검사의 한 조각이며 날씨 조인, 날짜 유효성, 중복 판단은 다음 레슨과 미션에서 확장합니다.

이 레슨의 프로젝트 적용과 검토

브라우저 입력은 교통량 열만 추려낸 JSON 목록입니다. 앞 단계 원본 CSV 전체를 붙여 넣는 것이 아니라 [100,"80",null]처럼 하나의 배열로 전달합니다. JSON의 null은 Python None으로 읽힙니다. 형식이 정상인 목록과 숫자 계약이 정상인 원소는 다른 검사 대상입니다. 이번 과제는 목록을 읽을 수 있다고 가정한 뒤 개별 원소의 숫자 여부를 판정합니다. 빈 목록에서 반복문이 실행되지 않아도 오류 목록은 처음에 생성되어 있으므로 []를 출력합니다.

ASCII 숫자 문자열만 받는 규칙에는 이유가 있습니다. 단위가 붙은 "100대"를 자동으로 잘라 읽으면 다른 입력의 "100km"도 같은 숫자가 될 수 있습니다. 쉼표도 국가마다 소수 구분에 쓰일 수 있으므로 명세 없이 없애지 않습니다. 앞뒤 공백 제거는 이 교육 계약에서 허용한 변경입니다. 원문과 정규화 값을 따로 이름 붙여 두면 어떤 부분을 바꾸었는지 설명할 수 있고 거부 행을 나중에 되짚기도 쉽습니다.

함수를 완성한 뒤 전부 정상인 테스트 하나만 확인하지 않습니다. JSON 정수0과 문자열"0"은 모두 정상이며 False와 null은 정상 정수가 아닙니다. 목록의 첫 원소가 오류일 때1이 반환되는지, 마지막 원소만 오류일 때 마지막 번호가 남는지 점검합니다. 출력에 행 번호 대신 원문을 넣으면 오류 위치 계약을 위반합니다. 코드를 보기 좋은 출력으로 바꾸기보다 먼저 정해진 결과와 일치시키고 추가 진단은 별도 기록으로 분리합니다.

미션의 교통 숫자 검사로 확장할 때는 빈값과 관측 수의 관계를 함께 읽습니다. 앞 모듈은 관측되지 않은 교통 합계를 빈칸으로 내보냈으므로 빈칸이 모두 잘못된 행은 아닙니다. 같은 파서라도 호출하는 지점의 계약에 따라 nullable을 정합니다. 날짜·중복·날씨 누락을 아직 확인하지 않은 수치 하나를 두고 전체 행이 정상이라고 말하지 않습니다. 이번 레슨의 성공 기준은 숫자 계약에 맞지 않는 위치를 빠짐없이 찾아내는 것입니다.

동료에게 결과를 전달할 때는 검사한 값의 범위도 적습니다. 비음수 정수라는 기준만으로 센서의 실제 가능한 최대값이나 일별 집계의 타당성을 보장하지는 않습니다. 현실적인 상한은 출처 명세와 운영 환경에 따라 정해야 합니다. 이 과제는 특정 장비의 상한을 임의로 만들지 않고 음수·소수·불린 같은 타입과 형식 오류에 집중합니다. 값 검사와 업무 의미 검사를 서로 다른 근거로 남기면 후속 검토자가 개선할 지점을 찾기 쉽습니다.

따라하기

문자열과 숫자 비교

아래 코드를 Python 3에서 실행합니다. 각 단계는 독립 예제입니다.

print('10' > '9')
print(int('10') > 9)
print(type('0').__name__, type(0).__name__)

실행 결과

False
True
str int

0과 결측을 구분

아래 코드를 Python 3에서 실행합니다. 각 단계는 독립 예제입니다.

for value in [0, None]:
    print(value, value is None, bool(value))

실행 결과

0 False False
None True False

JSON 행 번호 검사

아래 코드를 Python 3에서 실행합니다. 각 단계는 독립 예제입니다.

import json, re, sys
rows = json.load(sys.stdin)
errors = []
for number, value in enumerate(rows, 1):
    valid = (type(value) is int and value >= 0) or (isinstance(value, str) and re.fullmatch(r'[0-9]+', value.strip()) is not None)
    if not valid:
        errors.append(number)
print(json.dumps(errors))

실행 결과

[1, 2, 3, 4, 5, 6, 7]

확인 문제

실습

표준 입력은 통행량 값의 JSON 목록입니다. 0 이상의 JSON 정수 또는 앞뒤 공백을 제거한 ASCII 숫자 문자열만 정상입니다. 빈칸·null·불린·실수·음수·쉼표 문자열은 오류입니다. 오류 위치를 1부터 시작하는 번호 목록으로 입력 순서대로 JSON 한 줄 출력합니다. 빈 입력 목록 또는 전부 정상이면 []입니다.

모범 답안
import json, re, sys
rows = json.load(sys.stdin)
errors = []
for number, value in enumerate(rows, 1):
    valid = (type(value) is int and value >= 0) or (isinstance(value, str) and re.fullmatch(r'[0-9]+', value.strip()) is not None)
    if not valid:
        errors.append(number)
print(json.dumps(errors))

더 읽기

면접 질문

  • 결측값을 삭제할지 채울지 판단하는 과정을 설명해 주시면 됩니다.