Devin.KR

모듈과 실행 진입점

75분 안팎

학습 목표

import와 직접 실행을 구분합니다.

개념

함수를 가져오는 일과 프로그램을 시작하는 일을 분리합니다

다른 테스트가 저장 함수를 가져오기만 했는데 사용법 오류가 출력되고 프로세스가 종료된다면 재사용이 어렵습니다. Python은 import할 때 모듈의 최상위 문장을 실행합니다. 함수 정의만 있는 파일은 함수를 준비하지만 최상위에서 입력을 읽거나 main을 호출하면 가져오는 쪽의 동작까지 바뀝니다. 이번 레슨은 독서 기록의 파일 명령을 만들며 이 경계를 고칩니다.

앞 모듈은 persistence.py에 CSV 가져오기·JSON 저장, store.py에 ID 조회, reporting.py에 공개 SQL 보고서를 이미 구현했습니다. 새 file_cli.py는 이 함수를 조합하는 실행 진입점입니다. 계산·검증 코드를 복사해서 다시 구현하지 않습니다. 기존 app.py의 샘플 출력, 한 건 검사, --batch 형식과 HTTP 관찰 코드는 보존합니다. 새로운 파일 명령은 독립 파일에서만 추가합니다.

직접 실행을 main으로 묶습니다

def main(argv=None)은 명령 인자를 받아 실행 결과의 종료 코드를 반환합니다. 함수를 정의하는 순간에는 본문이 실행되지 않습니다. 파일 마지막의 if __name__ == "__main__": 조건이 참일 때만 main을 호출하고 SystemExit로 반환 코드를 프로세스에 전달합니다. python3 file_cli.py로 시작하면 이 조건이 참이고 import file_cli에서는 모듈 이름이 file_cli이므로 거짓입니다.

여기서 __name__은 Python이 현재 모듈에 설정하는 이름입니다. 파일 이름을 다른 이름으로 바꿨다고 직접 실행 조건이 깨지는 것은 아닙니다. 함수의 이름을 main으로 정하는 것은 관례이며 이름만 붙인다고 자동 실행되는 기능은 없습니다. 조건문과 호출이 실행을 연결합니다. import 검사는 출력이 없는지뿐 아니라 입력 요청·파일 생성 없이 끝나는지도 확인합니다.

argv=None이면 argparse는 실행 프로세스의 인자를 읽습니다. 테스트에서는 main(["lookup", "records.json", "b01"])처럼 문자열 목록을 전달할 수 있습니다. 이 목록에 python3나 스크립트 이름은 넣지 않습니다. 인자 해석을 함수 내부에 두어 import가 테스트 실행기의 인자를 잘못 읽지 않게 합니다. 최상위의 parser.parse_args 호출도 main 호출과 같은 부작용을 만들 수 있습니다.

명령별 입력과 책임을 정합니다

import 명령은 CSV 경로와 대상 JSON 경로를 받습니다. 전체 CSV를 검증한 후 기존 파일을 교체하고 IMPORTED|건수를 출력합니다. 이름이 import라는 CLI 하위 명령인 것과 Python의 import 문은 서로 다른 문법입니다. 기존 저장 함수의 전체 교체 계약을 사용하므로 기존 목록에 추가하는 명령으로 설명하지 않습니다. 헤더만 있는 CSV도 정상이며 대상은 빈 목록으로 바뀝니다.

lookup 명령은 대상 JSON과 ID를 받습니다. 기록이 있으면 다섯 필드의 JSON을 출력하고 종료 코드 0을 반환합니다. ID가 없으면 NOT_FOUND와 종료 코드 1을 반환합니다. load_store의 기존 계약상 없는 JSON 파일도 빈 저장소로 읽으므로 이 경우에도 없는 ID 결과가 나옵니다. 손상된 JSON을 빈 저장소로 취급하지는 않습니다. 손상은 파일 오류 처리 경로로 보내 원인을 숨기지 않습니다.

report 명령은 대상 JSON에서 공개 보고서를 만들고 JSON으로 출력합니다. rows, completed, total_pages 계약을 유지하며 공개 열에는 소유자 이름·이메일이 없습니다. 조회 명령의 저장 기록과 공개 보고서를 같은 결과로 취급하지 않습니다. CLI는 어떤 함수를 호출할지 선택하고 함수는 자기 데이터 계약을 지킵니다. 기존 SQL의 기본 경로는 reporting.py의 위치를 기준으로 찾습니다.

경로의 기준을 사용자에게 알려 줍니다

사용자가 인자로 입력한 data/import.csv나 records.json은 현재 작업 디렉터리 기준입니다. 반면 프로그램에 포함된 report.sql은 코드 파일 위치 기준입니다. 모든 경로에 __file__을 붙이면 사용자가 다른 폴더의 자료를 지정하는 뜻이 바뀝니다. 경로 종류마다 기준을 선택하고 문서에 씁니다. 테스트에서는 다른 작업 폴더에서 스크립트의 절대 경로와 임시 대상 파일을 사용해 이 차이를 확인합니다.

경로에 공백이 있으면 셸에서 따옴표로 묶습니다. python3 file_cli.py import "가상 자료/import.csv" records.json처럼 쓰면 하나의 인자로 전달됩니다. FileNotFoundError가 나면 현재 위치, 경로 철자, 파일 존재 순서로 확인합니다. 출력 파일의 부모 폴더는 이미 있어야 합니다. 이번 CLI는 폴더를 자동 생성하지 않으며 이 제한을 README에 알립니다.

오류를 좁게 처리하고 종료 상태를 남깁니다

파일 열기·쓰기 실패인 OSError와 자료 규칙 위반인 ValueError는 ERR_FILE|예외종류를 출력하고 종료 코드 2를 반환합니다. 전체 입력이나 개인 절대 경로는 이 오류 출력에 넣지 않습니다. 코딩 실수인 NameError까지 넓은 except Exception으로 숨기지 않습니다. 예상하지 않은 예외는 traceback으로 드러나야 개발자가 잘못된 위치를 찾을 수 있습니다.

명령 인자가 부족하거나 알 수 없는 하위 명령이면 argparse가 stderr에 usage와 error를 출력합니다. 이 경우 main의 파일 작업 전에 종료 코드 2로 끝납니다. stdout의 업무 결과와 stderr의 사용법 안내를 섞어서 판단하지 않습니다. 테스트의 실패 보고에서 반환 코드·stdout·stderr를 각각 비교하면 업무 검증 오류인지 인자 해석 실패인지 찾기 쉽습니다.

종료 코드 0은 이번 호출이 성공했다는 뜻입니다. 실패 뒤에도 기존 JSON이 보존되는지는 별도 검사가 필요합니다. 음수 쪽수 CSV를 가져온 뒤 대상 바이트가 그대로인지 대조합니다. 오류 메시지만 출력하고 기존 파일을 먼저 비운 구현은 이 테스트에서 실패합니다. CLI에서 저장 순서를 새로 만들지 않고 이미 검증한 import_csv를 호출하여 앞 모듈의 보호를 이어갑니다.

실습에서 빠진 경계를 찾습니다

starter는 직접 실행 조건 대신 if True를 사용하여 가져오기만 해도 main을 호출합니다. 명령으로 실행하면 여러 검사는 통과하므로 정상 사례만 보면 문제를 놓칩니다. tests/test_cli.py의 import 검사 실패를 읽고 호출 조건을 고칩니다. 테스트 파일은 변경하지 않습니다. solution에서는 일곱 검사 모두 통과하며 가져오기 출력, 다른 위치 실행, 없는 ID, 잘못된 CSV 보존, 인자 부족, 빈 CSV, 없는 원본을 확인합니다.

따라하기의 짧은 Python 코드는 이름과 인자 목록의 성질을 따로 관찰하는 예입니다. 실제 통합 명령은 lab ZIP 안에서 실행합니다. 두 종류의 실행 근거를 혼동하지 않습니다. 레슨을 마치면 가져와도 아무 입력을 요구하지 않는 file_cli 모듈과 직접 실행했을 때 종료 코드가 정해지는 명령을 모두 설명합니다. 패키지의 자세한 구성은 더 읽기로 이어갑니다.

따라하기

함수 정의는 호출이 아닙니다

함수를 준비한 뒤 명시적으로 호출하는 순서를 관찰합니다.

def main(argv):
    print("RUN", argv)
    return 0

print("DEFINED")
print(main(["lookup", "records.json", "b01"]))

실행 결과

DEFINED
RUN ['lookup', 'records.json', 'b01']
0

직접 실행과 import 이름 비교

같은 실행 조건이 모듈 이름에 따라 달라지는지 확인합니다. 실제 파일 import 부작용은 lab 테스트로 검증합니다.

for module_name in ["__main__", "file_cli"]:
    print(module_name, module_name == "__main__")

실행 결과

__main__ True
file_cli False

인자 목록에서 하위 명령 읽기

실행기 이름 없이 목록을 전달하고 필요한 인자를 해석합니다.

import argparse
p = argparse.ArgumentParser()
sub = p.add_subparsers(dest="command", required=True)
s = sub.add_parser("lookup")
s.add_argument("destination")
s.add_argument("id")
a = p.parse_args(["lookup", "records.json", "b01"])
print(a.command, a.destination, a.id)

실행 결과

lookup records.json b01

실제 파일 진입점 검사

실습 폴더에서 조건문을 고치고 일곱 검사를 실행합니다. stderr의 테스트 이름과 실패 비교를 읽습니다. 성공 기준은 일곱 검사와 OK입니다.

python3 -m unittest discover -s tests -p "test_cli.py"

확인 문제

실습

file_cli.py 마지막 호출 조건을 직접 실행에만 참이 되도록 고칩니다. 기존 app.py와 계산·저장 함수는 유지합니다. tests/test_cli.py는 수정하지 않습니다. 가져오기 2건, 없는 ID, 빈 CSV, 없는 원본, 오류 뒤 기존 JSON 보존, 인자 부족, 모듈 import 부작용을 확인합니다.

시작 코드·테스트 내려받기

실행 명령

python3 -m unittest discover -s tests -p "test_cli.py"

기대 결과

starter는 7개 중 import 부작용 1개 실패, solution은 7개와 OK.

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 다른 사람이 프로그램을 실행하도록 준비한 내용을 설명합니다.