[Python 100일 챌린지] Day 50 - 미니 프로젝트: 개인 가계부
Phase 5 완성! 🎉 내 손으로 만드는 가계부 앱! 😊
9일간 배운 모든 기술로 실전 가계부 프로그램 완성! 수입/지출 기록 → JSON 저장 → 월별 통계 → CSV 내보내기까지!
(50-60분 완독 ⭐⭐⭐⭐)
🎯 오늘의 학습 목표
📚 사전 지식
- Day 41: 파일 입출력 기초 - 파일 읽기/쓰기
- Day 43: JSON - JSON 데이터 처리
- Day 44: CSV - CSV 데이터 처리
- Day 45: 예외 처리 기초 - try-except
🎯 학습 목표 1: Phase 5 내용 복습하기
Phase 5에서 배운 내용
Day 41-44: 파일 입출력
- 파일 열기/닫기와 with 문
- 텍스트/바이너리 파일 처리
- JSON 데이터 직렬화
- CSV 파일 읽기/쓰기
Day 45-46: 예외 처리
- try-except-finally
- 다양한 예외 타입
- 사용자 정의 예외
- 예외 체이닝
Day 47-49: 로깅과 파일 시스템
- logging 모듈 활용
- 디버깅 기법
- os와 pathlib 모듈
- 파일/디렉토리 조작
오늘 만들 프로젝트
개인 가계부 - 실생활에서 바로 사용할 수 있는 프로그램!
1
2
3
4
5
6
7
📦 가계부 기능
├── 💰 수입/지출 기록
├── 📋 내역 조회 (전체, 월별)
├── 📊 월별 통계 (수입, 지출, 잔액)
├── 💾 JSON 파일 자동 저장
├── 📤 CSV 내보내기 (엑셀 호환)
└── 🔍 카테고리별 분석
🎯 학습 목표 2: 가계부 시스템 설계하기
프로젝트 목표
개인 가계부 프로그램 구현:
- 수입/지출 내역 추가
- 내역 조회 및 삭제
- 월별 통계 분석
- JSON 파일로 데이터 저장
- CSV로 내보내기
데이터 구조 설계
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 하나의 거래 내역
{
"id": 1,
"date": "2025-04-19",
"type": "지출", # "수입" 또는 "지출"
"category": "식비", # 카테고리
"amount": 15000, # 금액
"description": "점심 식사" # 설명
}
# 전체 데이터 파일 (budget.json)
{
"transactions": [...], # 거래 내역 리스트
"next_id": 2 # 다음 ID
}
카테고리 분류
1
2
3
4
5
# 수입 카테고리
INCOME_CATEGORIES = ["급여", "용돈", "부수입", "이자", "기타수입"]
# 지출 카테고리
EXPENSE_CATEGORIES = ["식비", "교통", "쇼핑", "문화", "의료", "교육", "공과금", "기타지출"]
🎯 학습 목표 3: 가계부 핵심 기능 구현하기
3.1 기본 설정과 데이터 로드/저장
💡 JSON 파일로 저장하는 이유: 프로그램을 종료해도 데이터가 유지됩니다. 다음에 프로그램을 실행하면 이전 기록을 불러올 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# budget_tracker.py
import json
import csv
from datetime import datetime
from pathlib import Path
# 데이터 파일 경로
DATA_FILE = "budget.json"
# 카테고리 정의
INCOME_CATEGORIES = ["급여", "용돈", "부수입", "이자", "기타수입"]
EXPENSE_CATEGORIES = ["식비", "교통", "쇼핑", "문화", "의료", "교육", "공과금", "기타지출"]
def load_data():
"""데이터 파일 불러오기"""
if Path(DATA_FILE).exists():
try:
with open(DATA_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError:
print("⚠️ 데이터 파일이 손상되었습니다. 새로 시작합니다.")
# 파일이 없거나 손상된 경우 빈 데이터 반환
return {"transactions": [], "next_id": 1}
def save_data(data):
"""데이터 파일 저장하기"""
with open(DATA_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print("💾 저장 완료!")
출력 예시:
1
💾 저장 완료!
3.2 수입/지출 추가 기능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def add_transaction(data):
"""새로운 수입/지출 추가"""
print("\n=== 💰 새 내역 추가 ===")
# 1. 수입/지출 선택
print("1. 수입")
print("2. 지출")
while True:
choice = input("선택 (1/2): ").strip()
if choice == "1":
trans_type = "수입"
categories = INCOME_CATEGORIES
break
elif choice == "2":
trans_type = "지출"
categories = EXPENSE_CATEGORIES
break
else:
print("❌ 1 또는 2를 입력하세요.")
# 2. 카테고리 선택
print(f"\n📁 {trans_type} 카테고리:")
for i, cat in enumerate(categories, 1):
print(f" {i}. {cat}")
while True:
try:
cat_num = int(input("카테고리 번호: "))
if 1 <= cat_num <= len(categories):
category = categories[cat_num - 1]
break
else:
print(f"❌ 1~{len(categories)} 사이의 숫자를 입력하세요.")
except ValueError:
print("❌ 숫자를 입력하세요.")
# 3. 금액 입력
while True:
try:
amount = int(input("금액 (원): "))
if amount > 0:
break
else:
print("❌ 0보다 큰 금액을 입력하세요.")
except ValueError:
print("❌ 숫자를 입력하세요.")
# 4. 설명 입력
description = input("설명 (선택, Enter로 건너뛰기): ").strip()
if not description:
description = "-"
# 5. 날짜 입력
date_input = input("날짜 (YYYY-MM-DD, Enter=오늘): ").strip()
if not date_input:
date_str = datetime.now().strftime("%Y-%m-%d")
else:
try:
# 날짜 형식 검증
datetime.strptime(date_input, "%Y-%m-%d")
date_str = date_input
except ValueError:
print("⚠️ 날짜 형식이 올바르지 않아 오늘 날짜로 저장합니다.")
date_str = datetime.now().strftime("%Y-%m-%d")
# 6. 내역 생성 및 저장
transaction = {
"id": data["next_id"],
"date": date_str,
"type": trans_type,
"category": category,
"amount": amount,
"description": description
}
data["transactions"].append(transaction)
data["next_id"] += 1
save_data(data)
# 결과 출력
print(f"\n✅ {trans_type} 추가 완료!")
print(f" 📅 {date_str} | {category} | {amount:,}원 | {description}")
실행 예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
=== 💰 새 내역 추가 ===
1. 수입
2. 지출
선택 (1/2): 2
📁 지출 카테고리:
1. 식비
2. 교통
3. 쇼핑
...
카테고리 번호: 1
금액 (원): 15000
설명 (선택, Enter로 건너뛰기): 점심 식사
날짜 (YYYY-MM-DD, Enter=오늘):
💾 저장 완료!
✅ 지출 추가 완료!
📅 2025-04-19 | 식비 | 15,000원 | 점심 식사
3.3 내역 조회 기능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def show_transactions(data, month=None):
"""내역 조회"""
transactions = data["transactions"]
if not transactions:
print("\n📭 등록된 내역이 없습니다.")
return
# 월별 필터링
if month:
filtered = [t for t in transactions if t["date"].startswith(month)]
title = f"📋 {month} 내역"
else:
filtered = transactions
title = "📋 전체 내역"
if not filtered:
print(f"\n📭 {month}에 해당하는 내역이 없습니다.")
return
# 날짜순 정렬 (최신순)
filtered.sort(key=lambda x: x["date"], reverse=True)
# 출력
print(f"\n{title}")
print("-" * 60)
print(f"{'ID':>4} | {'날짜':^12} | {'유형':^4} | {'카테고리':^8} | {'금액':>10} | 설명")
print("-" * 60)
for t in filtered:
type_emoji = "📈" if t["type"] == "수입" else "📉"
print(f"{t['id']:>4} | {t['date']:^12} | {type_emoji}{t['type']} | {t['category']:^8} | {t['amount']:>10,}원 | {t['description']}")
print("-" * 60)
print(f"총 {len(filtered)}건")
출력 예시:
1
2
3
4
5
6
7
8
9
📋 2025-04 내역
------------------------------------------------------------
ID | 날짜 | 유형 | 카테고리 | 금액 | 설명
------------------------------------------------------------
3 | 2025-04-19 | 📉지출 | 식비 | 15,000원 | 점심 식사
2 | 2025-04-18 | 📉지출 | 교통 | 5,000원 | 버스비
1 | 2025-04-15 | 📈수입 | 급여 | 3,000,000원 | 4월 월급
------------------------------------------------------------
총 3건
3.4 통계 기능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def show_statistics(data, month=None):
"""월별 통계"""
transactions = data["transactions"]
if not transactions:
print("\n📭 등록된 내역이 없습니다.")
return
# 월 선택
if not month:
month = input("통계 조회할 월 (YYYY-MM, Enter=이번 달): ").strip()
if not month:
month = datetime.now().strftime("%Y-%m")
# 해당 월 데이터 필터링
filtered = [t for t in transactions if t["date"].startswith(month)]
if not filtered:
print(f"\n📭 {month}에 해당하는 내역이 없습니다.")
return
# 통계 계산
total_income = sum(t["amount"] for t in filtered if t["type"] == "수입")
total_expense = sum(t["amount"] for t in filtered if t["type"] == "지출")
balance = total_income - total_expense
# 카테고리별 지출
expense_by_category = {}
for t in filtered:
if t["type"] == "지출":
cat = t["category"]
expense_by_category[cat] = expense_by_category.get(cat, 0) + t["amount"]
# 출력
print(f"\n📊 {month} 통계")
print("=" * 40)
print(f" 📈 총 수입: {total_income:>15,}원")
print(f" 📉 총 지출: {total_expense:>15,}원")
print("=" * 40)
# 잔액 색상 표시
if balance >= 0:
print(f" 💰 잔 액: {balance:>15,}원 ✅ 흑자")
else:
print(f" 💸 잔 액: {balance:>15,}원 ⚠️ 적자")
# 카테고리별 지출 상세
if expense_by_category:
print("\n📁 카테고리별 지출")
print("-" * 40)
# 금액 큰 순으로 정렬
sorted_expenses = sorted(expense_by_category.items(),
key=lambda x: x[1], reverse=True)
for category, amount in sorted_expenses:
# 비율 계산
ratio = (amount / total_expense * 100) if total_expense > 0 else 0
bar = "█" * int(ratio / 5) # 5% 당 1블록
print(f" {category:>8}: {amount:>10,}원 ({ratio:>5.1f}%) {bar}")
출력 예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
📊 2025-04 통계
========================================
📈 총 수입: 3,000,000원
📉 총 지출: 520,000원
========================================
💰 잔 액: 2,480,000원 ✅ 흑자
📁 카테고리별 지출
----------------------------------------
식비: 250,000원 ( 48.1%) █████████
교통: 120,000원 ( 23.1%) ████
쇼핑: 80,000원 ( 15.4%) ███
공과금: 70,000원 ( 13.5%) ██
3.5 내역 삭제 기능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def delete_transaction(data):
"""내역 삭제"""
# 먼저 내역 보여주기
show_transactions(data)
if not data["transactions"]:
return
# ID 입력
try:
target_id = int(input("\n삭제할 내역 ID (취소=0): "))
if target_id == 0:
print("❌ 삭제 취소")
return
except ValueError:
print("❌ 숫자를 입력하세요.")
return
# ID로 내역 찾기
for i, t in enumerate(data["transactions"]):
if t["id"] == target_id:
# 삭제 확인
print(f"\n삭제할 내역: {t['date']} | {t['type']} | {t['category']} | {t['amount']:,}원")
confirm = input("정말 삭제하시겠습니까? (y/n): ").strip().lower()
if confirm == 'y':
data["transactions"].pop(i)
save_data(data)
print("✅ 삭제 완료!")
else:
print("❌ 삭제 취소")
return
print(f"❌ ID {target_id}를 찾을 수 없습니다.")
3.6 CSV 내보내기 기능
💡 CSV로 내보내는 이유: 엑셀이나 구글 시트에서 열어서 더 자세한 분석을 할 수 있습니다!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def export_to_csv(data):
"""CSV 파일로 내보내기"""
transactions = data["transactions"]
if not transactions:
print("\n📭 내보낼 내역이 없습니다.")
return
# 파일명 생성
filename = f"budget_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
try:
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
# 헤더 작성
writer.writerow(["ID", "날짜", "유형", "카테고리", "금액", "설명"])
# 데이터 작성
for t in transactions:
writer.writerow([
t["id"],
t["date"],
t["type"],
t["category"],
t["amount"],
t["description"]
])
print(f"\n✅ CSV 내보내기 완료!")
print(f" 📄 파일: {filename}")
print(f" 📊 총 {len(transactions)}건")
except Exception as e:
print(f"❌ 내보내기 실패: {e}")
💡 encoding=’utf-8-sig’: 한글이 포함된 CSV를 엑셀에서 열 때 깨지지 않게 해주는 설정입니다!
3.7 메인 메뉴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def show_menu():
"""메인 메뉴 출력"""
print("\n" + "=" * 40)
print(" 💰 나만의 가계부 💰")
print("=" * 40)
print(" 1. 수입/지출 추가")
print(" 2. 내역 조회 (전체)")
print(" 3. 내역 조회 (월별)")
print(" 4. 통계 보기")
print(" 5. 내역 삭제")
print(" 6. CSV 내보내기")
print(" 0. 종료")
print("=" * 40)
def main():
"""메인 함수"""
print("\n🎉 가계부 프로그램을 시작합니다!")
# 데이터 불러오기
data = load_data()
print(f"📂 저장된 내역: {len(data['transactions'])}건")
while True:
show_menu()
choice = input("선택: ").strip()
if choice == "1":
add_transaction(data)
elif choice == "2":
show_transactions(data)
elif choice == "3":
month = input("조회할 월 (YYYY-MM): ").strip()
show_transactions(data, month)
elif choice == "4":
show_statistics(data)
elif choice == "5":
delete_transaction(data)
elif choice == "6":
export_to_csv(data)
elif choice == "0":
print("\n👋 가계부를 종료합니다. 안녕히 가세요!")
break
else:
print("❌ 올바른 메뉴를 선택하세요.")
if __name__ == "__main__":
main()
🎯 학습 목표 4: 프로젝트 완성하고 Phase 5 마무리
전체 코드 (복사해서 바로 실행!)
아래 코드를 budget_tracker.py 파일로 저장하고 실행하세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
"""
개인 가계부 프로그램
Python 100일 챌린지 Day 50 - Phase 5 프로젝트
"""
import json
import csv
from datetime import datetime
from pathlib import Path
# ===== 설정 =====
DATA_FILE = "budget.json"
INCOME_CATEGORIES = ["급여", "용돈", "부수입", "이자", "기타수입"]
EXPENSE_CATEGORIES = ["식비", "교통", "쇼핑", "문화", "의료", "교육", "공과금", "기타지출"]
# ===== 데이터 관리 =====
def load_data():
"""데이터 파일 불러오기"""
if Path(DATA_FILE).exists():
try:
with open(DATA_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
print("⚠️ 데이터 파일 오류. 새로 시작합니다.")
return {"transactions": [], "next_id": 1}
def save_data(data):
"""데이터 파일 저장하기"""
with open(DATA_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# ===== 핵심 기능 =====
def add_transaction(data):
"""새로운 수입/지출 추가"""
print("\n=== 💰 새 내역 추가 ===")
print("1. 수입 2. 지출")
while True:
choice = input("선택 (1/2): ").strip()
if choice in ["1", "2"]:
trans_type = "수입" if choice == "1" else "지출"
categories = INCOME_CATEGORIES if choice == "1" else EXPENSE_CATEGORIES
break
print("❌ 1 또는 2를 입력하세요.")
print(f"\n📁 카테고리:")
for i, cat in enumerate(categories, 1):
print(f" {i}. {cat}")
while True:
try:
cat_num = int(input("번호: "))
if 1 <= cat_num <= len(categories):
category = categories[cat_num - 1]
break
except ValueError:
pass
print(f"❌ 1~{len(categories)} 사이 숫자를 입력하세요.")
while True:
try:
amount = int(input("금액 (원): "))
if amount > 0:
break
except ValueError:
pass
print("❌ 0보다 큰 숫자를 입력하세요.")
description = input("설명 (Enter=생략): ").strip() or "-"
date_input = input("날짜 (YYYY-MM-DD, Enter=오늘): ").strip()
if date_input:
try:
datetime.strptime(date_input, "%Y-%m-%d")
date_str = date_input
except ValueError:
date_str = datetime.now().strftime("%Y-%m-%d")
print("⚠️ 형식 오류, 오늘 날짜로 저장")
else:
date_str = datetime.now().strftime("%Y-%m-%d")
transaction = {
"id": data["next_id"],
"date": date_str,
"type": trans_type,
"category": category,
"amount": amount,
"description": description
}
data["transactions"].append(transaction)
data["next_id"] += 1
save_data(data)
print(f"\n✅ {trans_type} 추가: {date_str} | {category} | {amount:,}원")
def show_transactions(data, month=None):
"""내역 조회"""
transactions = data["transactions"]
if not transactions:
print("\n📭 등록된 내역이 없습니다.")
return
filtered = [t for t in transactions if not month or t["date"].startswith(month)]
if not filtered:
print(f"\n📭 해당 내역이 없습니다.")
return
filtered.sort(key=lambda x: x["date"], reverse=True)
title = f"📋 {month} 내역" if month else "📋 전체 내역"
print(f"\n{title}")
print("-" * 65)
print(f"{'ID':>4} | {'날짜':^12} | {'유형':^6} | {'카테고리':^8} | {'금액':>12} | 설명")
print("-" * 65)
for t in filtered:
icon = "📈" if t["type"] == "수입" else "📉"
print(f"{t['id']:>4} | {t['date']:^12} | {icon}{t['type']} | {t['category']:^8} | {t['amount']:>10,}원 | {t['description']}")
print("-" * 65)
print(f"총 {len(filtered)}건")
def show_statistics(data):
"""통계 보기"""
transactions = data["transactions"]
if not transactions:
print("\n📭 등록된 내역이 없습니다.")
return
month = input("\n통계 월 (YYYY-MM, Enter=이번달): ").strip()
if not month:
month = datetime.now().strftime("%Y-%m")
filtered = [t for t in transactions if t["date"].startswith(month)]
if not filtered:
print(f"\n📭 {month} 내역이 없습니다.")
return
income = sum(t["amount"] for t in filtered if t["type"] == "수입")
expense = sum(t["amount"] for t in filtered if t["type"] == "지출")
balance = income - expense
print(f"\n📊 {month} 통계")
print("=" * 35)
print(f" 📈 수입: {income:>15,}원")
print(f" 📉 지출: {expense:>15,}원")
print("=" * 35)
print(f" 💰 잔액: {balance:>15,}원", "✅" if balance >= 0 else "⚠️")
# 카테고리별 지출
by_cat = {}
for t in filtered:
if t["type"] == "지출":
by_cat[t["category"]] = by_cat.get(t["category"], 0) + t["amount"]
if by_cat:
print("\n📁 카테고리별 지출")
print("-" * 35)
for cat, amt in sorted(by_cat.items(), key=lambda x: -x[1]):
ratio = amt / expense * 100 if expense else 0
bar = "█" * int(ratio / 5)
print(f" {cat:>6}: {amt:>10,}원 ({ratio:4.1f}%) {bar}")
def delete_transaction(data):
"""내역 삭제"""
show_transactions(data)
if not data["transactions"]:
return
try:
target_id = int(input("\n삭제할 ID (0=취소): "))
if target_id == 0:
return
except ValueError:
print("❌ 숫자를 입력하세요.")
return
for i, t in enumerate(data["transactions"]):
if t["id"] == target_id:
print(f"삭제: {t['date']} {t['type']} {t['category']} {t['amount']:,}원")
if input("확인 (y/n): ").lower() == 'y':
data["transactions"].pop(i)
save_data(data)
print("✅ 삭제 완료!")
return
print(f"❌ ID {target_id}를 찾을 수 없습니다.")
def export_csv(data):
"""CSV 내보내기"""
if not data["transactions"]:
print("\n📭 내보낼 내역이 없습니다.")
return
filename = f"budget_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(["ID", "날짜", "유형", "카테고리", "금액", "설명"])
for t in data["transactions"]:
writer.writerow([t["id"], t["date"], t["type"],
t["category"], t["amount"], t["description"]])
print(f"\n✅ 저장 완료: {filename} ({len(data['transactions'])}건)")
# ===== 메인 =====
def main():
print("\n🎉 가계부 시작!")
data = load_data()
print(f"📂 저장된 내역: {len(data['transactions'])}건")
while True:
print("\n" + "=" * 30)
print(" 💰 나만의 가계부")
print("=" * 30)
print("1. 수입/지출 추가")
print("2. 전체 내역 조회")
print("3. 월별 내역 조회")
print("4. 통계 보기")
print("5. 내역 삭제")
print("6. CSV 내보내기")
print("0. 종료")
print("=" * 30)
choice = input("선택: ").strip()
if choice == "1":
add_transaction(data)
elif choice == "2":
show_transactions(data)
elif choice == "3":
month = input("월 (YYYY-MM): ").strip()
show_transactions(data, month)
elif choice == "4":
show_statistics(data)
elif choice == "5":
delete_transaction(data)
elif choice == "6":
export_csv(data)
elif choice == "0":
print("\n👋 안녕히 가세요!")
break
else:
print("❌ 올바른 메뉴를 선택하세요.")
if __name__ == "__main__":
main()
프로젝트에서 사용한 Phase 5 기술
| Day | 기술 | 프로젝트 적용 |
|---|---|---|
| Day 41 | 파일 읽기/쓰기 | JSON 데이터 저장/로드 |
| Day 43 | JSON | 거래 내역 저장 |
| Day 44 | CSV | 내보내기 기능 |
| Day 45 | 예외 처리 | 입력 검증, 파일 오류 처리 |
| Day 49 | pathlib | 파일 존재 확인 |
💡 실전 팁 & 주의사항
✅ 코드 포인트
- 데이터 자동 저장: 추가/삭제 때마다
save_data()호출 - 입력 검증:
while True와try-except로 잘못된 입력 방지 - 한글 CSV:
encoding='utf-8-sig'로 엑셀 호환 - 날짜 검증:
datetime.strptime()으로 형식 확인
⚠️ 주의사항
budget.json파일을 직접 수정하면 데이터가 손상될 수 있음- 중요한 가계부 데이터는 CSV로 백업 권장
- 금액은 정수(원)로 저장 (소수점 필요시 코드 수정)
🧪 연습 문제
문제 1: 검색 기능 추가
목표: 설명에서 키워드로 검색하는 기능을 추가하세요.
요구사항:
- 키워드를 입력받아 설명에 포함된 내역만 출력
- 대소문자 구분 없이 검색
해답 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def search_transactions(data):
"""키워드 검색"""
keyword = input("검색어: ").strip().lower()
if not keyword:
print("❌ 검색어를 입력하세요.")
return
results = [t for t in data["transactions"]
if keyword in t["description"].lower()
or keyword in t["category"].lower()]
if not results:
print(f"📭 '{keyword}' 검색 결과가 없습니다.")
return
print(f"\n🔍 '{keyword}' 검색 결과: {len(results)}건")
print("-" * 60)
for t in results:
icon = "📈" if t["type"] == "수입" else "📉"
print(f"{t['date']} | {icon}{t['type']} | {t['category']} | {t['amount']:,}원 | {t['description']}")
문제 2: 연간 통계 기능
목표: 1년치 월별 수입/지출을 한눈에 보는 기능을 추가하세요.
요구사항:
- 연도를 입력받아 1~12월 통계 출력
- 각 월의 수입, 지출, 잔액 표시
해답 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def yearly_statistics(data):
"""연간 통계"""
year = input("연도 (YYYY, Enter=올해): ").strip()
if not year:
year = datetime.now().strftime("%Y")
print(f"\n📊 {year}년 연간 통계")
print("=" * 50)
print(f"{'월':^6} | {'수입':>12} | {'지출':>12} | {'잔액':>12}")
print("-" * 50)
total_income = 0
total_expense = 0
for month in range(1, 13):
month_str = f"{year}-{month:02d}"
filtered = [t for t in data["transactions"]
if t["date"].startswith(month_str)]
income = sum(t["amount"] for t in filtered if t["type"] == "수입")
expense = sum(t["amount"] for t in filtered if t["type"] == "지출")
balance = income - expense
total_income += income
total_expense += expense
if filtered: # 내역이 있는 월만 출력
print(f"{month:>4}월 | {income:>10,}원 | {expense:>10,}원 | {balance:>10,}원")
print("=" * 50)
print(f"{'합계':^6} | {total_income:>10,}원 | {total_expense:>10,}원 | {total_income - total_expense:>10,}원")
🎯 확장 아이디어
Level 1: 기본 기능 추가
- 검색 기능 (키워드, 날짜 범위)
- 예산 설정 및 알림
- 반복 지출 자동 등록
Level 2: 고급 기능
- 그래프 시각화 (matplotlib)
- 여러 계좌 관리
- 수입/지출 예측
Level 3: 앱 개발
- GUI 인터페이스 (tkinter)
- 웹 버전 (Flask)
- 모바일 앱 연동
📝 오늘 배운 내용 정리
| 개념 | 설명 | 프로젝트 적용 |
|---|---|---|
| JSON 저장 | 데이터 영구 보관 | 거래 내역 파일 저장 |
| CSV 내보내기 | 엑셀 호환 출력 | 데이터 백업 기능 |
| 입력 검증 | while + try-except | 금액, 날짜 입력 |
| pathlib | 파일 존재 확인 | 데이터 파일 체크 |
| datetime | 날짜 처리 | 오늘 날짜, 형식 검증 |
| 딕셔너리 | 데이터 구조화 | 거래 내역 저장 |
핵심 코드 패턴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 1. JSON 데이터 로드/저장
import json
from pathlib import Path
def load_data():
if Path("data.json").exists():
with open("data.json", 'r', encoding='utf-8') as f:
return json.load(f)
return {"items": []}
def save_data(data):
with open("data.json", 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# 2. 입력 검증 패턴
while True:
try:
value = int(input("숫자 입력: "))
if value > 0:
break
except ValueError:
pass
print("❌ 올바른 값을 입력하세요.")
# 3. CSV 내보내기 (엑셀 호환)
import csv
with open("export.csv", 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(["열1", "열2"]) # 헤더
writer.writerows(data) # 데이터
📝 Phase 5 전체 복습
| Day | 주제 | 핵심 내용 |
|---|---|---|
| 41 | 파일 기초 | open, read, write, with 문 |
| 42 | 텍스트 고급 | 정규표현식, 대용량 파일 |
| 43 | JSON | 직렬화, API 연동 |
| 44 | CSV | csv 모듈, Pandas |
| 45 | 예외 기초 | try-except, finally |
| 46 | 예외 고급 | 커스텀 예외, 체이닝 |
| 47 | 로깅 | logging 모듈, 핸들러 |
| 48 | 디버깅 | pdb, 프로파일링 |
| 49 | 파일 시스템 | pathlib, shutil |
| 50 | 프로젝트 | 개인 가계부 |
🔗 관련 자료
📚 이전 학습
Day 49: 파일 시스템 고급 ⭐⭐⭐
어제는 pathlib와 shutil로 파일과 디렉토리를 관리하는 방법을 배웠습니다!
📚 다음 학습
Phase 6: 웹 스크래핑과 API (Day 51-60) 🚀
다음 Phase에서는:
- requests 라이브러리로 HTTP 요청
- BeautifulSoup으로 웹 페이지 파싱
- API 연동
- 웹 크롤링 실전 프로젝트
“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀
Day 50/100 Phase 5 완료! #100DaysOfPython
이제와서 시작하는 Python 마스터하기 - Phase 5 완료! 🎉🎉🎉
10일간의 파일 처리와 예외 처리 여정을 마무리했습니다. 직접 만든 가계부로 이번 달부터 돈 관리를 시작해 보세요! 💰
