본문 바로가기
Python

실습: 주소록 프로그램 만들기

by 샤나엘 2026. 2. 23.
반응형

실습: 주소록 프로그램 만들기

실습: 주소록 프로그램 만들기

지금까지 배운 모든 것을 총동원하는 종합 프로젝트! 클래스, 파일I/O, 자료구조, 예외처리를 합쳐 실전 프로그램을 만들어봅시다.


프로젝트 개요

만들 기능:

  1. 연락처 추가 - 이름, 전화번호, 이메일 저장
  2. 연락처 검색 - 이름으로 검색
  3. 연락처 수정 - 정보 업데이트
  4. 연락처 삭제 - 연락처 제거
  5. 전체 목록 보기 - 모든 연락처 출력
  6. 파일 저장/불러오기 - 프로그램 종료 후에도 데이터 유지

주소록 프로그램 구조


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교육

반응형

댓글