반응형

실습: 주소록 프로그램 만들기
지금까지 배운 모든 것을 총동원하는 종합 프로젝트! 클래스, 파일I/O, 자료구조, 예외처리를 합쳐 실전 프로그램을 만들어봅시다.
프로젝트 개요
만들 기능:
- 연락처 추가 - 이름, 전화번호, 이메일 저장
- 연락처 검색 - 이름으로 검색
- 연락처 수정 - 정보 업데이트
- 연락처 삭제 - 연락처 제거
- 전체 목록 보기 - 모든 연락처 출력
- 파일 저장/불러오기 - 프로그램 종료 후에도 데이터 유지

Step 1: Contact 클래스 만들기
class Contact:
"""개별 연락처를 나타내는 클래스"""
def __init__(self, name, phone, email=""):
self.name = name
self.phone = phone
self.email = email
def __str__(self):
email_str = f" | {self.email}" if self.email else ""
return f"{self.name} | {self.phone}{email_str}"
def to_dict(self):
"""딕셔너리로 변환 (파일 저장용)"""
return {
"name": self.name,
"phone": self.phone,
"email": self.email
}
@classmethod
def from_dict(cls, data):
"""딕셔너리에서 Contact 생성"""
return cls(data["name"], data["phone"], data.get("email", ""))
to_dict()와from_dict()는 JSON 파일 저장/로드에 사용됩니다.
Step 2: AddressBook 클래스 만들기
import json
import os
class AddressBook:
"""주소록 관리 클래스"""
def __init__(self, filename="contacts.json"):
self.contacts = []
self.filename = filename
self.load() # 시작할 때 파일에서 불러오기
def add(self, name, phone, email=""):
"""연락처 추가"""
# 중복 확인
if self.find(name):
print(f"'{name}'은(는) 이미 존재합니다.")
return False
contact = Contact(name, phone, email)
self.contacts.append(contact)
self.save()
print(f"'{name}' 추가 완료!")
return True
def find(self, name):
"""이름으로 연락처 검색"""
for contact in self.contacts:
if contact.name == name:
return contact
return None
def search(self, keyword):
"""키워드로 연락처 검색 (부분 일치)"""
results = []
for contact in self.contacts:
if (keyword.lower() in contact.name.lower() or
keyword in contact.phone or
keyword.lower() in contact.email.lower()):
results.append(contact)
return results
def update(self, name, phone=None, email=None):
"""연락처 수정"""
contact = self.find(name)
if not contact:
print(f"'{name}'을(를) 찾을 수 없습니다.")
return False
if phone:
contact.phone = phone
if email:
contact.email = email
self.save()
print(f"'{name}' 수정 완료!")
return True
def delete(self, name):
"""연락처 삭제"""
contact = self.find(name)
if not contact:
print(f"'{name}'을(를) 찾을 수 없습니다.")
return False
self.contacts.remove(contact)
self.save()
print(f"'{name}' 삭제 완료!")
return True
def list_all(self):
"""전체 연락처 출력"""
if not self.contacts:
print("저장된 연락처가 없습니다.")
return
print(f"\n{'='*50}")
print(f" 전체 연락처 ({len(self.contacts)}명)")
print(f"{'='*50}")
for i, contact in enumerate(self.contacts, 1):
print(f" {i}. {contact}")
print(f"{'='*50}")
def save(self):
"""파일에 저장"""
data = [c.to_dict() for c in self.contacts]
with open(self.filename, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def load(self):
"""파일에서 불러오기"""
if not os.path.exists(self.filename):
return
try:
with open(self.filename, "r", encoding="utf-8") as f:
data = json.load(f)
self.contacts = [Contact.from_dict(d) for d in data]
except (json.JSONDecodeError, KeyError):
print("파일을 불러오는 중 오류가 발생했습니다.")
self.contacts = []
Step 3: 메인 프로그램 (메뉴 시스템)
def main():
book = AddressBook()
while True:
print("\n========= 주소록 =========")
print(" 1. 연락처 추가")
print(" 2. 연락처 검색")
print(" 3. 연락처 수정")
print(" 4. 연락처 삭제")
print(" 5. 전체 목록")
print(" 0. 종료")
print("==========================")
choice = input("메뉴 선택: ").strip()
if choice == "1":
name = input("이름: ").strip()
phone = input("전화번호: ").strip()
email = input("이메일 (없으면 Enter): ").strip()
book.add(name, phone, email)
elif choice == "2":
keyword = input("검색어: ").strip()
results = book.search(keyword)
if results:
print(f"\n검색 결과 ({len(results)}건):")
for contact in results:
print(f" - {contact}")
else:
print("검색 결과가 없습니다.")
elif choice == "3":
name = input("수정할 이름: ").strip()
contact = book.find(name)
if contact:
print(f"현재 정보: {contact}")
phone = input("새 전화번호 (유지하려면 Enter): ").strip()
email = input("새 이메일 (유지하려면 Enter): ").strip()
book.update(name, phone or None, email or None)
else:
print(f"'{name}'을(를) 찾을 수 없습니다.")
elif choice == "4":
name = input("삭제할 이름: ").strip()
confirm = input(f"'{name}'을(를) 정말 삭제할까요? (y/n): ")
if confirm.lower() == "y":
book.delete(name)
elif choice == "5":
book.list_all()
elif choice == "0":
print("프로그램을 종료합니다. 안녕!")
break
else:
print("잘못된 입력입니다. 0~5 사이의 숫자를 입력하세요.")
if __name__ == "__main__":
main()
전체 코드 (하나의 파일)
모든 코드를 address_book.py 하나에 정리하면:
전체 코드 보기
import json
import os
class Contact:
"""개별 연락처를 나타내는 클래스"""
def __init__(self, name, phone, email=""):
self.name = name
self.phone = phone
self.email = email
def __str__(self):
email_str = f" | {self.email}" if self.email else ""
return f"{self.name} | {self.phone}{email_str}"
def to_dict(self):
return {
"name": self.name,
"phone": self.phone,
"email": self.email
}
@classmethod
def from_dict(cls, data):
return cls(data["name"], data["phone"], data.get("email", ""))
class AddressBook:
"""주소록 관리 클래스"""
def __init__(self, filename="contacts.json"):
self.contacts = []
self.filename = filename
self.load()
def add(self, name, phone, email=""):
if self.find(name):
print(f"'{name}'은(는) 이미 존재합니다.")
return False
contact = Contact(name, phone, email)
self.contacts.append(contact)
self.save()
print(f"'{name}' 추가 완료!")
return True
def find(self, name):
for contact in self.contacts:
if contact.name == name:
return contact
return None
def search(self, keyword):
results = []
for contact in self.contacts:
if (keyword.lower() in contact.name.lower() or
keyword in contact.phone or
keyword.lower() in contact.email.lower()):
results.append(contact)
return results
def update(self, name, phone=None, email=None):
contact = self.find(name)
if not contact:
print(f"'{name}'을(를) 찾을 수 없습니다.")
return False
if phone:
contact.phone = phone
if email:
contact.email = email
self.save()
print(f"'{name}' 수정 완료!")
return True
def delete(self, name):
contact = self.find(name)
if not contact:
print(f"'{name}'을(를) 찾을 수 없습니다.")
return False
self.contacts.remove(contact)
self.save()
print(f"'{name}' 삭제 완료!")
return True
def list_all(self):
if not self.contacts:
print("저장된 연락처가 없습니다.")
return
print(f"\n{'='*50}")
print(f" 전체 연락처 ({len(self.contacts)}명)")
print(f"{'='*50}")
for i, contact in enumerate(self.contacts, 1):
print(f" {i}. {contact}")
print(f"{'='*50}")
def save(self):
data = [c.to_dict() for c in self.contacts]
with open(self.filename, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def load(self):
if not os.path.exists(self.filename):
return
try:
with open(self.filename, "r", encoding="utf-8") as f:
data = json.load(f)
self.contacts = [Contact.from_dict(d) for d in data]
except (json.JSONDecodeError, KeyError):
print("파일을 불러오는 중 오류가 발생했습니다.")
self.contacts = []
def main():
book = AddressBook()
while True:
print("\n========= 주소록 =========")
print(" 1. 연락처 추가")
print(" 2. 연락처 검색")
print(" 3. 연락처 수정")
print(" 4. 연락처 삭제")
print(" 5. 전체 목록")
print(" 0. 종료")
print("==========================")
choice = input("메뉴 선택: ").strip()
if choice == "1":
name = input("이름: ").strip()
phone = input("전화번호: ").strip()
email = input("이메일 (없으면 Enter): ").strip()
book.add(name, phone, email)
elif choice == "2":
keyword = input("검색어: ").strip()
results = book.search(keyword)
if results:
print(f"\n검색 결과 ({len(results)}건):")
for contact in results:
print(f" - {contact}")
else:
print("검색 결과가 없습니다.")
elif choice == "3":
name = input("수정할 이름: ").strip()
contact = book.find(name)
if contact:
print(f"현재 정보: {contact}")
phone = input("새 전화번호 (유지하려면 Enter): ").strip()
email = input("새 이메일 (유지하려면 Enter): ").strip()
book.update(name, phone or None, email or None)
else:
print(f"'{name}'을(를) 찾을 수 없습니다.")
elif choice == "4":
name = input("삭제할 이름: ").strip()
confirm = input(f"'{name}'을(를) 정말 삭제할까요? (y/n): ")
if confirm.lower() == "y":
book.delete(name)
elif choice == "5":
book.list_all()
elif choice == "0":
print("프로그램을 종료합니다. 안녕!")
break
else:
print("잘못된 입력입니다. 0~5 사이의 숫자를 입력하세요.")
if __name__ == "__main__":
main()
실행 예시
========= 주소록 =========
1. 연락처 추가
2. 연락처 검색
3. 연락처 수정
4. 연락처 삭제
5. 전체 목록
0. 종료
==========================
메뉴 선택: 1
이름: 김철수
전화번호: 010-1234-5678
이메일 (없으면 Enter): cs@email.com
'김철수' 추가 완료!
메뉴 선택: 1
이름: 이영희
전화번호: 010-9876-5432
이메일 (없으면 Enter):
'이영희' 추가 완료!
메뉴 선택: 5
==================================================
전체 연락처 (2명)
==================================================
1. 김철수 | 010-1234-5678 | cs@email.com
2. 이영희 | 010-9876-5432
==================================================
메뉴 선택: 2
검색어: 김
검색 결과 (1건):
- 김철수 | 010-1234-5678 | cs@email.com
저장 파일 형태
프로그램이 자동으로 생성하는 contacts.json:
[
{
"name": "김철수",
"phone": "010-1234-5678",
"email": "cs@email.com"
},
{
"name": "이영희",
"phone": "010-9876-5432",
"email": ""
}
]
사용된 개념 총정리
| 개념 | 사용 위치 | 배운 편 |
|---|---|---|
| 클래스와 객체 | Contact, AddressBook 클래스 | 26~28편 |
| 파일 I/O | save(), load() 메서드 | 23편 |
| JSON | 데이터 직렬화/역직렬화 | 30편 |
| 리스트 | contacts 저장, 검색 결과 | 12편 |
| 딕셔너리 | to_dict(), from_dict() | 13편 |
| 예외처리 | 파일 로드 시 에러 처리 | 22편 |
| 매직 메서드 | __str__, @classmethod | 28편 |
| 반복문/조건문 | 메뉴 시스템, 검색 로직 | 7~9편 |
| 함수 | main() 함수, 메서드들 | 17~18편 |
| 문자열 메서드 | strip(), lower(), f-string | 4~5편 |
도전 과제
주소록을 더 발전시켜 보세요!
도전 1: 전화번호 유효성 검사
# 힌트: 정규표현식 사용 (24편)
import re
def is_valid_phone(phone):
pattern = r"^01[0-9]-\d{3,4}-\d{4}$"
return bool(re.match(pattern, phone))
도전 2: 이름순 정렬 출력
# 힌트: sorted()의 key 활용 (31편)
def list_sorted(self):
sorted_contacts = sorted(self.contacts, key=lambda c: c.name)
for i, contact in enumerate(sorted_contacts, 1):
print(f" {i}. {contact}")
도전 3: CSV 내보내기
# 힌트: csv 모듈 활용 (23편)
import csv
def export_csv(self, csv_filename="contacts.csv"):
with open(csv_filename, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["이름", "전화번호", "이메일"])
for c in self.contacts:
writer.writerow([c.name, c.phone, c.email])
print(f"'{csv_filename}' 내보내기 완료!")
오늘의 정리
| 배운 것 | 핵심 |
|---|---|
| 프로젝트 설계 | 기능을 클래스로 분리하고 책임을 나눔 |
| 데이터 영속성 | JSON 파일로 데이터 저장/불러오기 |
| CRUD 패턴 | 추가(Create), 조회(Read), 수정(Update), 삭제(Delete) |
| 코드 통합 | 여러 개념을 하나의 프로그램으로 조합 |
다음 편 예고
드디어 마지막 편! 파이썬 독학 로드맵에서 앞으로의 학습 방향을 안내합니다. 웹 개발, 데이터 분석, 자동화 등 다양한 진로별 가이드를 준비했습니다!
파이썬 Python 파이썬독학 파이썬프로젝트 주소록 종합실습 파일입출력 클래스 파이썬실전 IT교육
반응형
'Python' 카테고리의 다른 글
| 다음 단계로 - 파이썬 독학 로드맵 (0) | 2026.02.23 |
|---|---|
| 알고리즘 기초 - 정렬과 탐색 (0) | 2026.02.23 |
| API 활용하기 - 다른 서비스와 대화하기 (0) | 2026.02.23 |
| 웹 스크래핑 - 인터넷에서 데이터 수집하기 (0) | 2026.02.23 |
| 28. 매직 메서드 - 파이썬 객체의 비밀 (0) | 2026.02.23 |
댓글