Python 기초부터 마스터까지 — 6화
모듈, 가상 환경, 파일과 JSON으로 프로젝트 구성하기
본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.
파일 하나로 시작한 프로그램도 기능이 늘어나면 역할별 경계가 필요하다. 모듈과 패키지, 가상 환경, 명시적인 파일 규칙을 이용하면 다른 환경에서도 프로젝트를 재현하기 쉬워진다.
모듈과 패키지
Python 파일 하나가 모듈이며 관련 모듈을 디렉터리 아래 묶은 것이 패키지다.
quest_app/
├─ pyproject.toml
├─ src/
│ └─ quest_app/
│ ├─ __init__.py
│ ├─ domain.py
│ ├─ service.py
│ └─ cli.py
└─ tests/
domain.py에는 퀘스트 규칙, service.py에는 사용 사례, cli.py에는 명령줄 입력과 출력을 둔다. 파일 크기보다 서로 다른 이유로 변경되는 코드를 나누는 것이 중요하다.
from quest_app.domain import Quest, QuestStatus
from module import *는 이름이 어디서 왔는지 흐리므로 공개할 이름을 명시하는 편이 좋다. import만 했는데 파일 연결이나 서버 시작 같은 큰 작업이 실행되지 않도록 최상위 부수 효과도 줄여야 한다.
프로젝트마다 가상 환경 만들기
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --editable .
가상 환경은 프로젝트별 Python 실행기와 설치 패키지의 경계를 나눈다. pip만 실행하기보다 python -m pip를 사용하면 현재 Python 환경에 패키지를 설치한다는 뜻이 분명해진다.
pyproject.toml에는 프로젝트 이름, 필요한 Python 버전, 의존성, 빌드 방식을 기록할 수 있다. 내 컴퓨터의 전역 환경에만 의존하지 말고 깨끗한 환경에서 설치와 실행을 확인해야 한다.
pathlib로 파일 다루기
from pathlib import Path
data_dir = Path("data")
data_dir.mkdir(parents=True, exist_ok=True)
save_path = data_dir / "quests.json"
save_path.write_text("[]", encoding="utf-8")
content = save_path.read_text(encoding="utf-8")
경로 문자열을 직접 이어 붙이기보다 Path를 사용하면 운영체제별 경로 구분자를 덜 신경 써도 된다. 텍스트 파일은 인코딩을 명시해야 환경에 따른 문제를 줄일 수 있다.
JSON은 문법만 보장한다
import json
from pathlib import Path
def load_quests(path: Path) -> list[dict]:
with path.open("r", encoding="utf-8") as file:
value = json.load(file)
if not isinstance(value, list):
raise ValueError("퀘스트 목록이 필요합니다.")
return value
json.load가 성공해도 데이터가 프로그램의 규칙을 만족한다는 뜻은 아니다. 각 항목에 필요한 키가 있는지, ID와 상태가 유효한지는 도메인 객체로 변환할 때 검사해야 한다.
파일을 열 때 with를 사용하면 정상 종료와 예외 모두에서 파일을 닫는다. 모듈 경계, 실행 환경, 파일 형식을 코드와 설정에 명시하면 프로젝트가 커져도 변경과 오류의 범위를 좁힐 수 있다.