⚙️
중급 (Intermediate)
OOP + JSON + 모듈 분리 + 패키지 구조
10주차 — 중급 종합 실습
OOP·예외·파일 I/O를 모두 사용한 미니 프로젝트 (단어장/가계부/로그 분석/연락처) 중 하나를 모듈 분리된 패키지 구조로 완성합니다.
프로젝트패키지OOPJSON
소요 시간
⏱ 3~4시간
난이도
📊 중급
선수 조건
🎯 1~9주차 전부
결과물
models/storage/cli 가 분리된 작은 패키지 앱
이 강의에서 배우는 것
- 1OOP·예외·파일 I/O를 모두 사용해 미니 프로젝트를 완성한다
- 2모듈 분리와 패키지 구조를 적용한다
1. 프로젝트 설계 시 고려사항
- 데이터 모델 — 어떤 클래스들로 표현?
- 영구 저장 — JSON? CSV? 어떤 형태?
- 사용자 인터페이스 — 명령어 형태? 메뉴 형태?
- 모듈 분리 — models.py, storage.py, cli.py 등
2. 권장 패키지 구조
text
my_app/
├── __init__.py
├── models.py # 클래스 정의 (Word, Book 등)
├── storage.py # JSON 저장/로드
├── cli.py # 메인 루프, 명령어 처리
└── main.py # 진입점3. 자주 하는 실수
- 모든 코드를 main.py 한 파일에 — 분리 안 하면 200줄 넘어가면 미궁
- JSON 직렬화 안 되는 객체 — dataclass + asdict() 활용
- 저장 시점이 잘못됨 — 매 변경 시 vs 종료 시 — 정책 정하기
- 예외를 너무 좁게 또는 너무 넓게 — 회복 가능한 곳에서만 except
4. FAQ
Q1. dataclass 를 써도 되나요?
권장. 단순 데이터 클래스는 dataclass 가 편함. (고급 1주차에서 자세히)
Q2. 단일 파일 vs 패키지?
단일 파일은 50줄 이하만. 그 이상이면 분리.
5. 다음 단계
중급 과정을 마쳤습니다! 🎉
- 고급 과정 — 타입 힌트, 데코레이터, 동시성, 테스트
- 실습 도전 — Lv3 중급종합 (가계부, 도서 관리, 일기장, 텍스트 어드벤처)
💻 예제 (examples)
실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.
wordbook_skeleton/models.py— Word, WordBook 클래스
CODE
from dataclasses import dataclass, field, asdict
@dataclass
class Word:
en: str
ko: str
correct: int = 0
wrong: int = 0
@dataclass
class WordBook:
words: list = field(default_factory=list)
def add(self, en, ko):
self.words.append(Word(en, ko))
def to_json(self):
return [asdict(w) for w in self.words]
▶ 실행 결과
(라이브러리 — 직접 실행 안 함)wordbook_skeleton/main.py— 진입점
CODE
from .models import WordBook
from .storage import load, save
def main():
book = load()
while True:
cmd = input("> ").strip()
if cmd == "quit":
save(book)
break
elif cmd.startswith("add "):
en, ko = cmd[4:].split(":")
book.add(en.strip(), ko.strip())
elif cmd == "list":
for w in book.words:
print(f"{w.en} = {w.ko}")
if __name__ == "__main__":
main()
▶ 실행 결과
> add apple:사과
> list
apple = 사과
> quit📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
과제 1
단어장 앱 (CRUD + JSON + 퀴즈 모드)
목표: 영-한 단어장을 JSON으로 영구 저장 + 무작위 퀴즈.
요구사항
- 패키지 구조: models.py / storage.py / cli.py / main.py
- 명령: add, list, del, quiz, quit
- quiz 모드는 5문제, 정답 즉시 알림 + 통계 업데이트
입출력 예시
> add apple:사과
> add book:책
> list
1. apple = 사과 (○0/×0)
2. book = 책 (○0/×0)
> quiz
[1/5] apple? 사과
정답!
...
종합: 4/5▶정답 코드 펼치기 / 접기
SOLUTION
# main.py — 통합 예시
import json, random
from pathlib import Path
from dataclasses import dataclass, field, asdict
PATH = Path("wordbook.json")
@dataclass
class Word:
en: str
ko: str
correct: int = 0
wrong: int = 0
def load():
if not PATH.exists(): return []
return [Word(**w) for w in json.loads(PATH.read_text(encoding="utf-8"))]
def save(words):
PATH.write_text(json.dumps([asdict(w) for w in words], ensure_ascii=False, indent=2), encoding="utf-8")
def quiz(words):
if not words: return
sample = random.sample(words, min(5, len(words)))
score = 0
for i, w in enumerate(sample, 1):
ans = input(f"[{i}/{len(sample)}] {w.en}? ").strip()
if ans == w.ko:
print("정답!"); w.correct += 1; score += 1
else:
print(f"오답. 정답: {w.ko}"); w.wrong += 1
print(f"종합: {score}/{len(sample)}")
words = load()
while True:
cmd = input("> ").strip()
if cmd == "quit": save(words); break
elif cmd.startswith("add "):
en, ko = cmd[4:].split(":"); words.append(Word(en.strip(), ko.strip()))
elif cmd == "list":
for i, w in enumerate(words, 1):
print(f"{i}. {w.en} = {w.ko} (○{w.correct}/×{w.wrong})")
elif cmd == "quiz": quiz(words)
▶ 실행 결과
> add apple:사과
> add book:책
> quiz
[1/2] apple? 사과
정답!
[2/2] book? 가방
오답. 정답: 책
종합: 1/2
> quit과제 2
간이 가계부 (수입/지출 + JSON + 카테고리)
목표: 수입/지출 기록 + 카테고리별 합계 + JSON 영속화.
요구사항
- 데이터 클래스 Entry(type, amount, category, memo, date)
- 명령: add, list, summary, quit
- summary는 카테고리별 합계 + 잔액
입출력 예시
> add 지출 5000 식비 점심
> add 수입 100000 월급 11월
> summary
수입: 100,000원
지출: 5,000원
잔액: 95,000원
[지출 카테고리]
식비: 5,000원▶정답 코드 펼치기 / 접기
SOLUTION
import json
from pathlib import Path
from datetime import date
from dataclasses import dataclass, asdict
PATH = Path("ledger.json")
@dataclass
class Entry:
type: str
amount: int
category: str
memo: str
date: str
def load():
if not PATH.exists(): return []
return [Entry(**e) for e in json.loads(PATH.read_text(encoding="utf-8"))]
def save(entries):
PATH.write_text(json.dumps([asdict(e) for e in entries], ensure_ascii=False, indent=2), encoding="utf-8")
def summary(entries):
inc = sum(e.amount for e in entries if e.type == "수입")
exp = sum(e.amount for e in entries if e.type == "지출")
print(f"수입: {inc:,}원")
print(f"지출: {exp:,}원")
print(f"잔액: {inc - exp:,}원")
print("\n[지출 카테고리]")
cat = {}
for e in entries:
if e.type == "지출":
cat[e.category] = cat.get(e.category, 0) + e.amount
for k, v in cat.items():
print(f"{k}: {v:,}원")
entries = load()
while True:
cmd = input("> ").strip()
if cmd == "quit": save(entries); break
elif cmd == "summary": summary(entries)
elif cmd.startswith("add "):
t, amt, cat, *memo = cmd[4:].split()
entries.append(Entry(t, int(amt), cat, " ".join(memo), str(date.today())))
▶ 실행 결과
> add 지출 5000 식비 점심
> add 수입 100000 월급
> summary
수입: 100,000원
지출: 5,000원
잔액: 95,000원
[지출 카테고리]
식비: 5,000원과제 3
로그 분석기 (정규식 + CSV 리포트)
목표: 로그 파일에서 LEVEL/시각/메시지를 정규식으로 분리해 CSV로 리포트.
요구사항
- 로그 형식: LEVEL YYYY-MM-DD HH:MM 메시지
- 결과를 report.csv (level, time, message) 로 저장
- ERROR 개수, WARN 개수, INFO 개수 집계 출력
입출력 예시
처리: 4건
ERROR: 2, WARN: 1, INFO: 1
report.csv 저장됨▶정답 코드 펼치기 / 접기
SOLUTION
import re, csv
from pathlib import Path
log_text = """INFO 2026-05-09 14:30 서버 시작
ERROR 2026-05-09 14:32 인증 실패
WARN 2026-05-09 14:34 느린 응답
ERROR 2026-05-09 14:35 DB 연결 끊김"""
pattern = re.compile(r"(\w+) \d{4}-\d{2}-\d{2} (\d{2}:\d{2}) (.+)")
rows = []
for line in log_text.splitlines():
m = pattern.match(line)
if m:
rows.append(m.groups())
counts = {}
for level, _, _ in rows:
counts[level] = counts.get(level, 0) + 1
with open("report.csv", "w", encoding="utf-8", newline="") as f:
w = csv.writer(f)
w.writerow(["level", "time", "message"])
w.writerows(rows)
print(f"처리: {len(rows)}건")
print(", ".join(f"{k}: {v}" for k, v in counts.items()))
print("report.csv 저장됨")
▶ 실행 결과
처리: 4건
INFO: 1, ERROR: 2, WARN: 1
report.csv 저장됨