함수와 센서 입력 검증
100분 안팎
학습 목표
단위가 mm인 정수 입력을 m로 변환하며 경계값과 빈 입력을 테스트합니다.
개념
변환을 함수로 분리하는 이유
거리 변환을 입력 루프에만 넣으면 다른 기록 경로에서 같은 규칙을 다시 작성하게 됩니다. 한쪽은 1000으로 나누고 다른 쪽은 100으로 나누는 실수도 생깁니다. 이번 레슨은 정수 mm를 받아 m 값을 반환하는 mm_to_m 함수를 만들고 호출 결과를 검사합니다. 입력을 읽는 책임, 단위와 범위를 확인하는 책임, 결과를 표시하는 책임을 나누면 실패가 어느 단계에서 발생했는지 설명할 수 있습니다. 센서가 내보내는 단위를 함수 경계에서 고정하는 것이 목적입니다.
입력 계약과 경계값
브라우저 입력은 공백으로 나눈 정수 mm 토큰이며 개수를 나타내는 첫 줄은 없습니다. 50mm부터 8000mm까지 두 끝값을 포함합니다. 함수는 유효한 정수에서 m 실수를 반환하고 범위 밖 정수에는 None을 반환합니다. 정수로 해석되지 않는 토큰은 호출부에서 INVALID로 출력합니다. 입력이 비어 있으면 거리 상태를 만들어 내지 않고 EMPTY 한 줄을 출력합니다. 이 구분을 README 없이도 읽을 수 있도록 실습 프롬프트와 테스트에 적습니다.
단위는 수의 크기와 함께 읽습니다. 1250mm는 1.25m이며 계산은 value / 1000입니다. Python의 /는 정수를 나누어도 실수 결과를 만듭니다. //는 바닥 나눗셈이라 1250 // 1000은 1이 되어 거리의 세부 정보가 사라집니다. 50 // 1000은 0이므로 최소 거리에서도 잘못된 값이 됩니다. 실수로 바꾸기 전 정수 mm 경계에서 먼저 검사하면 소수 변환 뒤에 경계를 다시 비교하는 단계가 필요하지 않습니다.
매개변수와 반환값
def mm_to_m(value):에서 value는 호출자가 제공하는 값을 함수 안에서 부르는 매개변수입니다. 함수의 들여쓴 블록이 끝나기 전에 return value / 1000으로 결과를 돌려줍니다. 함수 안에서 print만 해서는 호출자가 계산 결과를 받을 수 없습니다. return이 없으면 암묵적으로 None이 돌아가며, 호출부에서 소수점 형식으로 출력하려 할 때 TypeError가 나올 수 있습니다. 변환 함수는 화면 출력 없이 값을 반환하고 출력 형식은 호출부가 결정합니다.
유효하지 않은 입력을 0으로 바꾸면 실제 0이라는 측정과 실패 표식이 섞입니다. 여기서는 None이라는 별도 상태를 사용합니다. 호출부는 result is None으로 검사한 뒤 INVALID를 출력하고, 그렇지 않으면 숫자를 표시합니다. if not result처럼 쓰면 값 0도 거짓으로 판단되므로 다른 범위로 확장할 때 숨은 오류가 됩니다. 이번 범위에서 0은 유효하지 않지만 실패 검사 자체는 None을 직접 확인하는 습관으로 작성합니다.
문자열을 읽는 경계
int(token)은 정수 문자열을 정수 값으로 변환합니다. 1250은 가능하지만 1250.5는 ValueError가 발생합니다. float를 거쳐 int로 바꾸면 1250.5가 1250으로 잘려 입력 계약 위반이 숨겨집니다. 그래서 호출부는 int(token)을 직접 시도하고 ValueError만 처리합니다. 숫자로 읽힌 -1은 파싱 실패가 아니라 범위 실패입니다. 같은 INVALID 출력이라도 디버깅할 때 두 경로를 구분하면 단위 입력 문제인지 센서 범위 문제인지 판단할 수 있습니다.
함수를 Python 코드에서 직접 호출하는 경우에는 문자열뿐 아니라 True나 실수도 들어올 수 있습니다. 변환 함수에서 type(value) is int를 검사해 정수만 허용합니다. Python의 bool은 int의 하위 형식이므로 isinstance(value, int)만으로는 True를 막지 못합니다. 실제 기록 계약에서는 참·거짓을 거리 숫자로 받아들이지 않아야 합니다. 함수 이름만 보고 타입이 강제될 것이라고 기대하지 않고, 허용 형식을 실행되는 조건으로 적습니다.
출력 정밀도와 값의 의미
화면은 유효 거리마다 소수점 아래 세 자리로 출력합니다. f"{result:.3f}"는 0.05를 0.050으로, 8.0을 8.000으로 표시합니다. 이것은 출력 계약이며 내부 값을 문자열로 바꾸어 다음 계산에 전달하라는 뜻은 아닙니다. 변환 함수의 반환값은 숫자로 유지하고 표시할 때만 형식을 적용합니다. 1.250처럼 출력한다고 센서가 그 정밀도를 실제로 제공한다는 의미도 아닙니다. 여기서는 입력 정수 mm를 읽기 쉽게 m로 표기하는 데 사용합니다.
잘못된 토큰이 하나 있어도 다른 토큰의 변환을 계속합니다. 각 토큰마다 한 줄을 출력하므로 입력 순서와 결과 순서를 대조할 수 있습니다. None을 목록에서 지우고 나머지만 출력하면 어떤 입력이 실패했는지 위치를 잃습니다. 이번 출력은 INVALID를 같은 자리에 남기는 방식입니다. 실제 파일 처리에서는 행 번호를 함께 남기는 방식으로 발전시킵니다. 문자열을 비교해 정렬하거나 단위를 생략한 숫자 목록만 보관하는 것은 이 레슨의 반환 계약과 다릅니다.
테스트를 값 묶음으로 설계하기
테스트는 정상값 하나만 확인하지 않습니다. 50과 8000으로 포함 경계를 확인하고 49와 8001로 경계 바깥을 확인합니다. 빈 입력은 아무 호출도 없는 경우를 검증합니다. 1250은 정수 나눗셈을 잘못 사용했을 때 발견하기 좋은 값이고, 1.5 같은 토큰은 정수가 아닌 입력을 몰래 반올림하지 않는지 확인합니다. 테스트 입력을 한꺼번에 넣기 전에 각 함수 호출의 반환을 예상하면 출력 오류와 계산 오류를 더 쉽게 분리할 수 있습니다.
함수 테스트에서는 결과가 실수인지와 값이 맞는지를 확인합니다. 1250의 결과를 1.25로 기대하고 None 여부는 identity 검사로 확인합니다. TypeError: unsupported format string passed to NoneType.__format__은 유효성 검사 없이 None에 숫자 형식을 적용했다는 신호입니다. ZeroDivisionError가 보인다면 프로젝트에 필요하지 않은 가변 나눗수를 추가했는지 살펴봅니다. 나눗수 1000은 mm와 m 사이의 단위 관계로 고정되며 빈 입력과 관계가 없습니다.
다음 입력 계층에 넘길 결과
함수 안에서 전역 목록을 수정하거나 이전 호출 결과를 쌓지 않습니다. 같은 입력을 다시 넣으면 같은 결과가 나오도록 만듭니다. 리스트를 기본 인자로 두어 결과를 누적하면 호출 사이에 값이 남을 수 있으므로 이번 함수는 단일 값만 처리합니다. 이 작은 결정성은 이후 센서 기록을 재생할 때 중요합니다. 호출 순서가 달라져도 변환 결과가 바뀌지 않아야 시각이나 좌표 변환 문제를 분리해 시험할 수 있습니다.
실습을 마칠 때는 단위 변환 함수와 표준 입력 호출부를 각각 설명합니다. 함수는 int mm에서 float m 또는 None으로 가는 경계이며 호출부는 문자열을 읽고 실패 상태를 표시합니다. 실제 JSONL 메시지에는 순번과 시각, 좌표계 이름도 함께 들어갑니다. 그것들은 다음 레슨에서 필드별로 검사하되, 여기서 만든 정수 계약을 JSON 숫자 전체에 그대로 적용하지 않습니다. 범위와 단위는 동일해도 입력 표현 방식에 맞는 별도의 검증 책임이 필요합니다.
따라하기
단위 나눗셈 비교
일반 나눗셈과 바닥 나눗셈의 차이를 1250mm로 확인합니다.
print(1250 / 1000)
print(1250 // 1000)
실행 결과
1.25 1
변환 함수 실행
유효 값은 숫자로 돌려받아 출력하고, 범위 밖 값과 bool은 None인지 확인합니다.
def mm_to_m(value):
if type(value) is not int or not 50 <= value <= 8000:
return None
return value / 1000
for value in [49, 50, 1250, 8000, 8001, True]:
result = mm_to_m(value)
print("INVALID" if result is None else f"{result:.3f}")
실행 결과
INVALID 0.050 1.250 8.000 INVALID INVALID
파싱 오류와 반환 누락 읽기
예외를 일부러 재현하되 잡아서 종류를 출력합니다. 정수가 아닌 토큰과 반환 없는 함수를 구분합니다.
try:
int("12.5")
except ValueError as error:
print(type(error).__name__)
def missing_return(value):
value / 1000
print(missing_return(1250))
print("EMPTY" if not " ".split() else "DATA")
실행 결과
ValueError None EMPTY
확인 문제
실습
정수 mm 토큰을 받아 mm_to_m(value)를 구현합니다. type(value)가 int이고 50~8000 포함이면 m 숫자를 반환하며 나머지는 None입니다. 입력은 건수 머리말 없이 공백으로 나눕니다. 호출부는 정수로 읽지 못한 토큰 또는 None에 INVALID, 유효 값에 소수점 세 자리 결과를 한 줄씩 출력합니다. 빈 입력은 EMPTY 한 줄입니다.
모범 답안
import sys
def mm_to_m(value):
if type(value) is not int or not 50 <= value <= 8000:
return None
return value / 1000
tokens = sys.stdin.read().split()
if not tokens:
print("EMPTY")
for token in tokens:
try:
value = int(token)
except ValueError:
print("INVALID")
continue
result = mm_to_m(value)
print("INVALID" if result is None else f"{result:.3f}")
더 읽기
면접 질문
- 센서의 단위 변환 함수를 어떤 경계값으로 테스트합니까?