← 파이썬 강의 목록으로
⚙️
중급 (Intermediate)
OOP + JSON + 모듈 분리 + 패키지 구조

10주차 — 중급 종합 실습

OOP·예외·파일 I/O를 모두 사용한 미니 프로젝트 (단어장/가계부/로그 분석/연락처) 중 하나를 모듈 분리된 패키지 구조로 완성합니다.

프로젝트패키지OOPJSON
소요 시간
3~4시간
난이도
📊 중급
선수 조건
🎯 1~9주차 전부
결과물
models/storage/cli 가 분리된 작은 패키지 앱

이 강의에서 배우는 것

  • 1OOP·예외·파일 I/O를 모두 사용해 미니 프로젝트를 완성한다
  • 2모듈 분리와 패키지 구조를 적용한다

1. 프로젝트 설계 시 고려사항

  1. 데이터 모델 — 어떤 클래스들로 표현?
  2. 영구 저장 — JSON? CSV? 어떤 형태?
  3. 사용자 인터페이스 — 명령어 형태? 메뉴 형태?
  4. 모듈 분리 — models.py, storage.py, cli.py 등

2. 권장 패키지 구조

text
my_app/
├── __init__.py
├── models.py       # 클래스 정의 (Word, Book 등)
├── storage.py      # JSON 저장/로드
├── cli.py          # 메인 루프, 명령어 처리
└── main.py         # 진입점

3. 자주 하는 실수

  1. 모든 코드를 main.py 한 파일에 — 분리 안 하면 200줄 넘어가면 미궁
  2. JSON 직렬화 안 되는 객체 — dataclass + asdict() 활용
  3. 저장 시점이 잘못됨 — 매 변경 시 vs 종료 시 — 정책 정하기
  4. 예외를 너무 좁게 또는 너무 넓게 — 회복 가능한 곳에서만 except

4. FAQ

Q1. dataclass 를 써도 되나요?

권장. 단순 데이터 클래스는 dataclass 가 편함. (고급 1주차에서 자세히)

Q2. 단일 파일 vs 패키지?

단일 파일은 50줄 이하만. 그 이상이면 분리.

5. 다음 단계

중급 과정을 마쳤습니다! 🎉

  • 고급 과정 — 타입 힌트, 데코레이터, 동시성, 테스트
  • 실습 도전 — Lv3 중급종합 (가계부, 도서 관리, 일기장, 텍스트 어드벤처)

💻 예제 (examples)

실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.

wordbook_skeleton/models.pyWord, 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 저장됨
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗