← 파이썬 강의 목록으로
💰
실습 프로젝트 (Practice) · ★★★
JSON 영구 저장 + 카테고리

Lv3 · 가계부

수입/지출 기록을 JSON 으로 영구 저장. 카테고리별 합계, 잔액, 막대그래프 시각화까지 포함합니다.

JSON예외집계
소요 시간
3~5시간
난이도
📊 중급 종합
선수 조건
🎯 중급 5-6주차 (예외, JSON I/O)
결과물
영구 저장 패턴과 집계

이 강의에서 배우는 것

  • 1JSON 으로 영구 저장
  • 2카테고리별 집계와 잔액 계산
  • 3잘못된 입력에 대한 친절한 예외 처리

프로젝트 개요

  • ledger.json 에 기록 영구 저장
  • 명령: add / list / summary / del <번호> / quit
  • summary: 카테고리별 합계 + 막대그래프

도전 과제

  • 월별 집계 (2026-05 필터)
  • 한 줄 명령 (add 지출 5000 식비 점심)

채점 체크리스트

  • JSON 손상 안전 처리
  • 예외 처리
  • 막대그래프 출력

💻 예제 (examples)

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

solution.py전체 동작 코드
CODE
"""가계부 — JSON 영구 저장"""
import json
from pathlib import Path
from datetime import date

DB = Path("ledger.json")

def load():
    if not DB.exists():
        return []
    try:
        return json.loads(DB.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        print("⚠️ ledger.json 손상 — 빈 장부로 시작")
        return []

def save(records):
    DB.write_text(
        json.dumps(records, ensure_ascii=False, indent=2),
        encoding="utf-8"
    )

def add(records):
    t = input("수입/지출: ").strip()
    if t not in ("수입", "지출"):
        print("'수입' 또는 '지출'"); return
    try:
        amount = int(input("금액: "))
        if amount < 0:
            raise ValueError
    except ValueError:
        print("양의 정수 입력"); return
    category = input("카테고리: ").strip()
    memo = input("메모: ").strip()
    records.append({
        "type": t, "amount": amount, "category": category,
        "memo": memo, "date": date.today().isoformat(),
    })
    save(records)
    print("추가됨")

def show(records):
    if not records:
        print("(비어있음)"); return
    for i, r in enumerate(records, 1):
        sign = "+" if r["type"] == "수입" else "-"
        print(f"{i:>3}. {r['date']} {sign}{r['amount']:>10,}원 [{r['category']:<6}] {r['memo']}")

def summary(records):
    income = sum(r["amount"] for r in records if r["type"] == "수입")
    expense = sum(r["amount"] for r in records if r["type"] == "지출")
    print(f"수입: {income:>12,}원")
    print(f"지출: {expense:>12,}원")
    print(f"잔액: {income - expense:>12,}원")

    by_cat = {}
    for r in records:
        if r["type"] == "지출":
            by_cat[r["category"]] = by_cat.get(r["category"], 0) + r["amount"]
    if by_cat:
        max_amt = max(by_cat.values())
        print("\n[지출 카테고리별]")
        for cat, amt in sorted(by_cat.items(), key=lambda x: -x[1]):
            bar = "#" * int(40 * amt / max_amt)
            print(f"  {cat:<8} {amt:>10,}  {bar}")

def main():
    records = load()
    print(f"불러옴: {len(records)}건")
    while True:
        cmd = input("\n명령(add/list/summary/del <n>/quit): ").strip()
        if not cmd: continue
        if cmd == "quit": break
        elif cmd == "add": add(records)
        elif cmd == "list": show(records)
        elif cmd == "summary": summary(records)
        elif cmd.startswith("del "):
            try:
                idx = int(cmd.split()[1]) - 1
                if 0 <= idx < len(records):
                    del records[idx]; save(records); print("삭제")
            except (ValueError, IndexError):
                print("올바른 번호 입력")

if __name__ == "__main__":
    main()
▶ 실행 결과
불러옴: 0건
명령: add
수입/지출: 지출
금액: 5000
카테고리: 식비
메모: 점심
추가됨
명령: summary
수입:            0원
지출:        5,000원
잔액:       -5,000원

[지출 카테고리별]
  식비           5,000  ########################################

📝 과제 (exercises)

직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.

과제 1

프로젝트 구현

목표: 위 사양대로 직접 작성한다.

요구사항
  • JSON 영구 저장
  • summary 막대그래프
  • 예외 처리
💡 힌트

json.dumps(..., ensure_ascii=False, indent=2)

입출력 예시
잔액:       -5,000원
채점
  • · 저장 안전
  • · 집계 정확
  • · 예외 안전
정답 코드 펼치기 / 접기
SOLUTION
# 위 solution.py 와 동일
▶ 실행 결과
(예제 참고)
예제 코드 / 강의 자료

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

GitHub에서 보기 ↗