미션: 도서 관리 시스템 구축
*개요
최종 목표: 도서관의 책 대여 및 반납을 관리하는 시스템을 구축하고,
대여 통계를 파일로 저장하는 프로그램을 완성합니다.
핵심 요구사항:
➢ 책 정보를 담는 클래스 구조 설계
➢ 대여/반납 기능 구현
➢ 사용자 입력 처리 및 예외 처리
➢ 대여 기록을 파일로 저장하고 불러오기
➢ 통계 정보 출력 기능
제공 데이터:
도서 목록:
1. 파이썬 완전정복 (저자: 김코딩, ISBN: 9781234567890, 재고: 3권)
2. 데이터 과학 입문 (저자: 이데이터, ISBN: 9781234567891, 재고: 2권)
3. 웹 개발의 모든 것 (저자: 박웹개, ISBN: 9781234567892, 재고: 5권)
4. AI 프로그래밍 (저자: 최인공, ISBN: 9781234567893, 재고: 1권)
5. 알고리즘 기초 (저자: 정알고, ISBN: 9781234567894, 재고: 4권)
(필요에 따라 자유롭게 설정)
미션 1: 데이터 구조 설계하기
도서 정보를 효율적으로 관리하기 위한 데이터 구조를 설계하세요.
요구사항:
➢ 책의 제목, 저자, ISBN, 재고 수량을 저장할 수 있어야 함
➢ Book 클래스를 설계
➢ 최소 5권의 책 정보를 저장하는 리스트 생성
➢ 책 정보를 출력하는 함수 작성
미션 2: 대여 및 반납 기능 구현하기
도서 대여와 반납 기능을 구현하세요.
요구사항:
➢ 책 제목으로 대여할 수 있는 borrow_book() 함수
➢ 재고가 0이면 대여 불가 처리
➢ 대여 시 재고 감소, 반납 시 재고 증가
➢ return_book() 함수 구현
➢ 예외 처리: 존재하지 않는 책 제목 입력 시
힌트:
➢ 리스트에서 특정 책을 찾는 방법 고려
➢ 조건문으로 재고 확인
➢ try-except로 예외 상황 처리
미션 3: 대여 기록 관리하기
대여 기록을 저장하고 관리하는 기능을 추가하세요.
요구사항:
➢ 대여 기록을 저장하는 리스트 또는 딕셔너리 생성
➢ 대여 시 기록 추가 (책 제목, 대여 날짜/시간)
➢ 대여 기록을 조회하는 함수
➢ datetime 모듈 활용
힌트:
➢ datetime 모듈의 datetime.now() 사용
➢ 딕셔너리로 {'title': '책제목', 'date': '날짜'} 형태 저장
➢ 리스트에 여러 대여 기록 딕셔너리 추가
미션 4: 파일 저장 및 불러오기
대여 기록과 현재 재고 상태를 파일로 저장하고 불러오는 기능을 구현하세요.
요구사항:
➢ 대여 기록을 텍스트 파일로 저장
➢ 현재 책 재고 상태를 파일로 저장
➢ 프로그램 시작 시 파일에서 데이터 불러오기
➢ 파일이 없을 때 예외 처리
힌트:
➢ open() 함수와 with 문 사용
➢ FileNotFoundError 예외 처리
➢ 문자열 포맷팅으로 데이터 정리
미션 5: 메뉴 시스템 및 통계 기능
사용자가 쉽게 사용할 수 있는 메뉴 시스템과 통계 기능을 구현하세요.
요구사항:
➢ 반복문을 사용한 메뉴 시스템
➢ 도서 목록 보기, 2. 대여하기, 3. 반납하기, 4. 대여 기록, 5. 통계 보기, 6.
종료
➢ 통계: 총 대여 횟수, 가장 많이 대여된 책
➢ 잘못된 입력에 대한 예외 처리
힌트:
➢ while True 반복문과 break 사용
➢ 딕셔너리로 책별 대여 횟수 카운트
➢ max() 함수로 최대값 찾기
추가 도전 과제!
내가 작성한 프롬프트
넌 파이썬을 가르치는 선생님이야 도서관의 책 대여 및 반납을 관리하는 시스템을 구축을 해야 해.
요구사항
1. 클래스: 책 정보를 담는 클래스 구현
2. 기능: 대여 반납기능구현
3. 사용자 입력 처리 input, 예외처리
4. 대여기록을 파일로 저장 및 불러오기
5. 통계 정보 출력 기능
제공 데이터:
도서 목록:
1. 파이썬 완전정복 (저자: 김코딩, ISBN: 9781234567890, 재고: 3권)
2. 데이터 과학 입문 (저자: 이데이터, ISBN: 9781234567891, 재고: 2권)
3. 웹 개발의 모든 것 (저자: 박웹개, ISBN: 9781234567892, 재고: 5권)
4. AI 프로그래밍 (저자: 최인공, ISBN: 9781234567893, 재고: 1권)
5. 알고리즘 기초 (저자: 정알고, ISBN: 9781234567894, 재고: 4권)
으로 각 딕셔너리를 리스트에 데이터 저장해줘
각 단계 별로 진행해야 해
1. 데이터 구조 설계하기
요구사항:
1) 책 제목, 저자, ISBN, 재고수량을 딕셔너리 형태로 책정보 저장
2) Book클래스 설계
3) 리스트는 최소 5권의 책 정보를 저장
4) 책 정보 출력함수 생성
2. 대여 및 반납 기능 구현하기
요구사항:
1) 책 제목으로 대여하는 함수 borrow_book생성
2) 재고가 0일시 대출 불가 if문으로 충족할시 print("대출 불가합니다.")
3) 대여시 재고감소, 반납시 재고 증가하게끔 구현
4) 반납 함수 return_book
5) 예외 처리: 존재하지 않는 책 제목 입력시 예외 발생
3. 대여 기록 관리하기
요구사항:
1) 대여기록을 저장하는 리스트 생성 리스트안에 딕셔너리 저장하는 형태[{},{},{}]
2) 대여 시 리스트에 대출 정보 추가 (책, 제목, 대여 날짜/시간)
3) 대여 기록을 조회하는 select_borrow함수 추가
4) Datetime모듈 활용
4. 파일 저장 및 불러오기
대여 기록과 현재 재고 상태를 파일로 저장및 출력기능 구현
요구사항:
1) 대여 기록을 텍스트 파일로 저장 책, 제목, 대여 날짜.시간
2) 현재 책 재고 상태를 파일 저장 파이썬 완전정복 (저자: 김코딩, ISBN: 9781234567890, 재고: 3권)
3) 프로그램 시작 시 파일에서 데이터 불러오기 open(), with문사용
4) 파일 없을시 예외처리
5. 메뉴 시스템 및 통계 기능
사용자가 쉽게 사용할 수 있는 메뉴 시스템과 통계 기능을 구현하세요.
요구사항:
1) While True문을 활용한 도서 메뉴선택
2) 도서 목록 보기, 2. 대여하기, 3.반납하기, 4.대여기록, 5.통계 보기, 6. 종료
3) 통계: 총 대여 횟수, 가장 많이 대여된 책 max함수로 추출 (제목만)
4) 숫자 입력 잘못시 예외처리
제미나이가 만든 결과물
import datetime
from collections import Counter
class Book:
"""책 정보를 담는 클래스"""
def __init__(self, title, author, isbn, stock):
self.title = title
self.author = author
self.isbn = isbn
# 재고 수량은 정수형으로 저장
self.stock = int(stock)
def __str__(self):
"""책 정보를 보기 좋게 출력하는 메서드"""
return f"제목: {self.title}, 저자: {self.author}, ISBN: {self.isbn}, 재고: {self.stock}권"
# 초기 도서 목록 데이터 (딕셔너리 리스트)
initial_books_data = [
{"title": "파이썬 완전정복", "author": "김코딩", "isbn": "9781234567890", "stock": 3},
{"title": "데이터 과학 입문", "author": "이데이터", "isbn": "9781234567891", "stock": 2},
{"title": "웹 개발의 모든 것", "author": "박웹개", "isbn": "9781234567892", "stock": 5},
{"title": "AI 프로그래밍", "author": "최인공", "isbn": "9781234567893", "stock": 1},
{"title": "알고리즘 기초", "author": "정알고", "isbn": "9781234567894", "stock": 4},
]
# Book 객체를 저장할 리스트
books = []
def display_books():
"""현재 도서 목록 정보를 출력하는 함수"""
if not books:
print("도서 목록이 비어 있습니다.")
return
print("\n📚 **현재 도서 목록** 📚")
print("-" * 30)
for book in books:
# Book 클래스의 __str__ 메서드를 활용
print(book)
print("-" * 30)
# 초기 Book 객체 생성 및 리스트에 저장
# 이 과정은 4단계에서 파일 불러오기로 대체될 수 있지만, 초기 구동을 위해 여기서 실행합니다.
for data in initial_books_data:
book = Book(data["title"], data["author"], data["isbn"], data["stock"])
books.append(book)
# display_books()
def borrow_book(title):
"""
책 제목으로 책을 대여하는 함수.
대여 성공 시 해당 Book 객체를 반환, 실패 시 None 반환.
"""
try:
# 책 제목으로 목록에서 책을 찾습니다.
book_to_borrow = next(book for book in books if book.title == title)
except StopIteration:
# 존재하지 않는 책 제목 입력 시 예외 처리
raise ValueError(f"'{title}' 책은 도서 목록에 존재하지 않습니다.")
# 재고 확인
if book_to_borrow.stock > 0:
book_to_borrow.stock -= 1
print(f"✅ '{title}' 책이 대여되었습니다. 남은 재고: {book_to_borrow.stock}권")
return book_to_borrow
else:
# 재고가 0일 경우 대출 불가
print(f"❌ '{title}' 책은 현재 **대출 불가**합니다. (재고: 0)")
return None
### **반납 함수 (`return_book`)**
def return_book(title):
"""책 제목으로 책을 반납하고 재고를 증가시키는 함수"""
try:
# 책 제목으로 목록에서 책을 찾습니다.
book_to_return = next(book for book in books if book.title == title)
except StopIteration:
# 존재하지 않는 책 제목 입력 시 예외 처리
raise ValueError(f"'{title}' 책은 도서 목록에 존재하지 않습니다.")
# 재고 증가
book_to_return.stock += 1
print(f"✅ '{title}' 책이 반납되었습니다. 현재 재고: {book_to_return.stock}권")
return book_to_return
# 대여 기록을 저장할 리스트. [{}, {}, ...] 형태
borrow_records = []
def record_borrow(book):
"""대여 성공 시 대여 기록 리스트에 정보를 추가하는 함수"""
# 현재 날짜와 시간을 'YYYY-MM-DD HH:MM:SS' 형식의 문자열로 저장
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
record = {
"title": book.title,
"isbn": book.isbn,
"borrow_time": now
}
borrow_records.append(record)
print(f"⭐ 대여 기록이 저장되었습니다. (대여 시각: {now})")
def select_borrow():
"""대여 기록을 조회하고 출력하는 함수"""
if not borrow_records:
print("\n📝 **현재 대여 기록이 없습니다.**")
return
print("\n📝 **전체 도서 대여 기록** 📝")
print("-" * 50)
for record in borrow_records:
print(f"책 제목: {record['title']}, ISBN: {record['isbn']}, 대여 시각: {record['borrow_time']}")
print("-" * 50)
BORROW_RECORDS_FILE = "borrow_records.txt"
BOOK_STOCK_FILE = "book_stock.txt"
def save_data():
"""대여 기록과 현재 재고 상태를 파일로 저장하는 함수"""
print("\n💾 **데이터 저장 중...**")
# 1. 대여 기록 저장 (title, isbn, borrow_time)
try:
with open(BORROW_RECORDS_FILE, 'w', encoding='utf-8') as f:
for record in borrow_records:
# 콤마(,)로 구분하여 한 줄에 저장
line = f"{record['title']},{record['isbn']},{record['borrow_time']}\n"
f.write(line)
print(f"✅ 대여 기록이 '{BORROW_RECORDS_FILE}'에 저장되었습니다.")
except Exception as e:
print(f"❌ 대여 기록 저장 중 오류 발생: {e}")
# 2. 현재 책 재고 상태 저장 (title, author, isbn, stock)
try:
with open(BOOK_STOCK_FILE, 'w', encoding='utf-8') as f:
for book in books:
# 콤마(,)로 구분하여 한 줄에 저장
line = f"{book.title},{book.author},{book.isbn},{book.stock}\n"
f.write(line)
print(f"✅ 책 재고 상태가 '{BOOK_STOCK_FILE}'에 저장되었습니다.")
except Exception as e:
print(f"❌ 책 재고 저장 중 오류 발생: {e}")
def load_data():
"""파일에서 대여 기록과 책 재고 상태를 불러오는 함수"""
print("\n📥 **데이터 불러오는 중...**")
global books
global borrow_records
# 1. 책 재고 상태 불러오기
try:
books.clear() # 기존 목록 초기화
with open(BOOK_STOCK_FILE, 'r', encoding='utf-8') as f:
for line in f:
# 각 줄을 콤마(,)로 분리
title, author, isbn, stock = line.strip().split(',')
# Book 객체를 생성하여 books 리스트에 추가
books.append(Book(title, author, isbn, int(stock)))
print(f"✅ 책 재고 상태를 '{BOOK_STOCK_FILE}'에서 불러왔습니다.")
except FileNotFoundError:
# 파일이 없을 경우 예외 처리
print(f"⚠️ '{BOOK_STOCK_FILE}' 파일이 없어 초기 데이터를 사용합니다.")
# 초기 데이터를 다시 로드 (4단계 이전에 이미 로드되어 있으므로 생략 가능하나, 안전을 위해 구조화)
except Exception as e:
print(f"❌ 책 재고 불러오기 중 오류 발생: {e}")
# 2. 대여 기록 불러오기
try:
borrow_records.clear() # 기존 기록 초기화
with open(BORROW_RECORDS_FILE, 'r', encoding='utf-8') as f:
for line in f:
# 각 줄을 콤마(,)로 분리
title, isbn, borrow_time = line.strip().split(',')
# 딕셔너리 형태로 borrow_records 리스트에 추가
borrow_records.append({
"title": title,
"isbn": isbn,
"borrow_time": borrow_time
})
print(f"✅ 대여 기록을 '{BORROW_RECORDS_FILE}'에서 불러왔습니다.")
except FileNotFoundError:
# 파일이 없을 경우 예외 처리
print(f"⚠️ '{BORROW_RECORDS_FILE}' 파일이 없어 새로운 대여 기록을 시작합니다.")
except Exception as e:
print(f"❌ 대여 기록 불러오기 중 오류 발생: {e}")
# 프로그램 시작 시 데이터 불러오기 (초기 데이터 로드 후에 실행하여 기존 데이터를 덮어씀)
# load_data()
def show_statistics():
"""총 대여 횟수 및 가장 많이 대여된 책을 출력하는 함수"""
print("\n📊 **도서관 통계 정보** 📊")
print("-" * 30)
# 1. 총 대여 횟수
total_borrows = len(borrow_records)
print(f"**총 대여 횟수:** {total_borrows}회")
# 2. 가장 많이 대여된 책 (제목만)
if borrow_records:
# 대여 기록에서 모든 책 제목을 추출
titles = [record['title'] for record in borrow_records]
# Counter를 사용하여 각 책 제목의 등장 횟수(대여 횟수)를 계산
borrow_counts = Counter(titles)
# 가장 많이 대여된 책의 제목과 횟수를 추출
most_borrowed = borrow_counts.most_common(1) # 가장 흔한 항목 1개를 리스트로 반환
# most_borrowed는 [('책 제목', 횟수)] 형태
most_title = most_borrowed[0][0]
most_count = most_borrowed[0][1]
print(f"**가장 많이 대여된 책:** '{most_title}' ({most_count}회)")
else:
print("아직 대여 기록이 없어 통계를 계산할 수 없습니다.")
print("-" * 30)
def main():
"""메인 메뉴 시스템을 구동하는 함수"""
# 4단계: 프로그램 시작 시 데이터 불러오기
# 초기 데이터를 Book 객체로 변환하는 과정은 load_data() 내에서 처리됩니다.
load_data()
# 만약 파일이 없어 초기 데이터를 사용했다면, books 리스트가 비어있을 수 있으므로 다시 채웁니다.
# (실습의 일관성을 위해 1단계에서 이미 채웠다면 이 부분은 건너뛰어도 됩니다.)
if not books:
for data in initial_books_data:
books.append(Book(data["title"], data["author"], data["isbn"], data["stock"]))
while True:
print("\n*** 📖 도서 관리 시스템 메뉴 📖 ***")
print("1. 도서 목록 보기")
print("2. 대여하기")
print("3. 반납하기")
print("4. 대여 기록 조회")
print("5. 통계 보기")
print("6. 종료")
print("-" * 30)
choice = input("메뉴를 선택하세요 (1-6): ").strip()
try:
if choice == '1':
display_books()
elif choice == '2':
# 대여
title = input("대여할 책 제목을 입력하세요: ").strip()
borrowed_book = borrow_book(title)
if borrowed_book:
record_borrow(borrowed_book) # 대여 성공 시 기록 추가
elif choice == '3':
# 반납
title = input("반납할 책 제목을 입력하세요: ").strip()
return_book(title)
elif choice == '4':
# 대여 기록 조회
select_borrow()
elif choice == '5':
# 통계 보기
show_statistics()
elif choice == '6':
# 종료 시 파일 저장
print("\n프로그램을 종료합니다. 현재 데이터를 저장합니다.")
save_data()
break
else:
# 숫자 입력 잘못시 예외처리 (여기서는 범위 외의 문자를 포함하여 처리)
print("⚠️ **오류:** 1부터 6 사이의 유효한 메뉴 번호를 입력해 주세요.")
except ValueError as e:
# 존재하지 않는 책 제목 입력 등 사용자 입력 관련 예외 처리
print(f"❌ **사용자 입력 오류:** {e}")
except Exception as e:
# 기타 예상치 못한 오류 처리
print(f"❌ **시스템 오류 발생:** {e}")
# 프로그램 실행
if __name__ == "__main__":
main()import datetime
from collections import Counter
class Book:
"""책 정보를 담는 클래스"""
def __init__(self, title, author, isbn, stock):
self.title = title
self.author = author
self.isbn = isbn
# 재고 수량은 정수형으로 저장
self.stock = int(stock)
def __str__(self):
"""책 정보를 보기 좋게 출력하는 메서드"""
return f"제목: {self.title}, 저자: {self.author}, ISBN: {self.isbn}, 재고: {self.stock}권"
# 초기 도서 목록 데이터 (딕셔너리 리스트)
initial_books_data = [
{"title": "파이썬 완전정복", "author": "김코딩", "isbn": "9781234567890", "stock": 3},
{"title": "데이터 과학 입문", "author": "이데이터", "isbn": "9781234567891", "stock": 2},
{"title": "웹 개발의 모든 것", "author": "박웹개", "isbn": "9781234567892", "stock": 5},
{"title": "AI 프로그래밍", "author": "최인공", "isbn": "9781234567893", "stock": 1},
{"title": "알고리즘 기초", "author": "정알고", "isbn": "9781234567894", "stock": 4},
]
# Book 객체를 저장할 리스트
books = []
def display_books():
"""현재 도서 목록 정보를 출력하는 함수"""
if not books:
print("도서 목록이 비어 있습니다.")
return
print("\n📚 **현재 도서 목록** 📚")
print("-" * 30)
for book in books:
# Book 클래스의 __str__ 메서드를 활용
print(book)
print("-" * 30)
# 초기 Book 객체 생성 및 리스트에 저장
# 이 과정은 4단계에서 파일 불러오기로 대체될 수 있지만, 초기 구동을 위해 여기서 실행합니다.
for data in initial_books_data:
book = Book(data["title"], data["author"], data["isbn"], data["stock"])
books.append(book)
# display_books()
def borrow_book(title):
"""
책 제목으로 책을 대여하는 함수.
대여 성공 시 해당 Book 객체를 반환, 실패 시 None 반환.
"""
try:
# 책 제목으로 목록에서 책을 찾습니다.
book_to_borrow = next(book for book in books if book.title == title)
except StopIteration:
# 존재하지 않는 책 제목 입력 시 예외 처리
raise ValueError(f"'{title}' 책은 도서 목록에 존재하지 않습니다.")
# 재고 확인
if book_to_borrow.stock > 0:
book_to_borrow.stock -= 1
print(f"✅ '{title}' 책이 대여되었습니다. 남은 재고: {book_to_borrow.stock}권")
return book_to_borrow
else:
# 재고가 0일 경우 대출 불가
print(f"❌ '{title}' 책은 현재 **대출 불가**합니다. (재고: 0)")
return None
### **반납 함수 (`return_book`)**
def return_book(title):
"""책 제목으로 책을 반납하고 재고를 증가시키는 함수"""
try:
# 책 제목으로 목록에서 책을 찾습니다.
book_to_return = next(book for book in books if book.title == title)
except StopIteration:
# 존재하지 않는 책 제목 입력 시 예외 처리
raise ValueError(f"'{title}' 책은 도서 목록에 존재하지 않습니다.")
# 재고 증가
book_to_return.stock += 1
print(f"✅ '{title}' 책이 반납되었습니다. 현재 재고: {book_to_return.stock}권")
return book_to_return
# 대여 기록을 저장할 리스트. [{}, {}, ...] 형태
borrow_records = []
def record_borrow(book):
"""대여 성공 시 대여 기록 리스트에 정보를 추가하는 함수"""
# 현재 날짜와 시간을 'YYYY-MM-DD HH:MM:SS' 형식의 문자열로 저장
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
record = {
"title": book.title,
"isbn": book.isbn,
"borrow_time": now
}
borrow_records.append(record)
print(f"⭐ 대여 기록이 저장되었습니다. (대여 시각: {now})")
def select_borrow():
"""대여 기록을 조회하고 출력하는 함수"""
if not borrow_records:
print("\n📝 **현재 대여 기록이 없습니다.**")
return
print("\n📝 **전체 도서 대여 기록** 📝")
print("-" * 50)
for record in borrow_records:
print(f"책 제목: {record['title']}, ISBN: {record['isbn']}, 대여 시각: {record['borrow_time']}")
print("-" * 50)
BORROW_RECORDS_FILE = "borrow_records.txt"
BOOK_STOCK_FILE = "book_stock.txt"
def save_data():
"""대여 기록과 현재 재고 상태를 파일로 저장하는 함수"""
print("\n💾 **데이터 저장 중...**")
# 1. 대여 기록 저장 (title, isbn, borrow_time)
try:
with open(BORROW_RECORDS_FILE, 'w', encoding='utf-8') as f:
for record in borrow_records:
# 콤마(,)로 구분하여 한 줄에 저장
line = f"{record['title']},{record['isbn']},{record['borrow_time']}\n"
f.write(line)
print(f"✅ 대여 기록이 '{BORROW_RECORDS_FILE}'에 저장되었습니다.")
except Exception as e:
print(f"❌ 대여 기록 저장 중 오류 발생: {e}")
# 2. 현재 책 재고 상태 저장 (title, author, isbn, stock)
try:
with open(BOOK_STOCK_FILE, 'w', encoding='utf-8') as f:
for book in books:
# 콤마(,)로 구분하여 한 줄에 저장
line = f"{book.title},{book.author},{book.isbn},{book.stock}\n"
f.write(line)
print(f"✅ 책 재고 상태가 '{BOOK_STOCK_FILE}'에 저장되었습니다.")
except Exception as e:
print(f"❌ 책 재고 저장 중 오류 발생: {e}")
def load_data():
"""파일에서 대여 기록과 책 재고 상태를 불러오는 함수"""
print("\n📥 **데이터 불러오는 중...**")
global books
global borrow_records
# 1. 책 재고 상태 불러오기
try:
books.clear() # 기존 목록 초기화
with open(BOOK_STOCK_FILE, 'r', encoding='utf-8') as f:
for line in f:
# 각 줄을 콤마(,)로 분리
title, author, isbn, stock = line.strip().split(',')
# Book 객체를 생성하여 books 리스트에 추가
books.append(Book(title, author, isbn, int(stock)))
print(f"✅ 책 재고 상태를 '{BOOK_STOCK_FILE}'에서 불러왔습니다.")
except FileNotFoundError:
# 파일이 없을 경우 예외 처리
print(f"⚠️ '{BOOK_STOCK_FILE}' 파일이 없어 초기 데이터를 사용합니다.")
# 초기 데이터를 다시 로드 (4단계 이전에 이미 로드되어 있으므로 생략 가능하나, 안전을 위해 구조화)
except Exception as e:
print(f"❌ 책 재고 불러오기 중 오류 발생: {e}")
# 2. 대여 기록 불러오기
try:
borrow_records.clear() # 기존 기록 초기화
with open(BORROW_RECORDS_FILE, 'r', encoding='utf-8') as f:
for line in f:
# 각 줄을 콤마(,)로 분리
title, isbn, borrow_time = line.strip().split(',')
# 딕셔너리 형태로 borrow_records 리스트에 추가
borrow_records.append({
"title": title,
"isbn": isbn,
"borrow_time": borrow_time
})
print(f"✅ 대여 기록을 '{BORROW_RECORDS_FILE}'에서 불러왔습니다.")
except FileNotFoundError:
# 파일이 없을 경우 예외 처리
print(f"⚠️ '{BORROW_RECORDS_FILE}' 파일이 없어 새로운 대여 기록을 시작합니다.")
except Exception as e:
print(f"❌ 대여 기록 불러오기 중 오류 발생: {e}")
# 프로그램 시작 시 데이터 불러오기 (초기 데이터 로드 후에 실행하여 기존 데이터를 덮어씀)
# load_data()
def show_statistics():
"""총 대여 횟수 및 가장 많이 대여된 책을 출력하는 함수"""
print("\n📊 **도서관 통계 정보** 📊")
print("-" * 30)
# 1. 총 대여 횟수
total_borrows = len(borrow_records)
print(f"**총 대여 횟수:** {total_borrows}회")
# 2. 가장 많이 대여된 책 (제목만)
if borrow_records:
# 대여 기록에서 모든 책 제목을 추출
titles = [record['title'] for record in borrow_records]
# Counter를 사용하여 각 책 제목의 등장 횟수(대여 횟수)를 계산
borrow_counts = Counter(titles)
# 가장 많이 대여된 책의 제목과 횟수를 추출
most_borrowed = borrow_counts.most_common(1) # 가장 흔한 항목 1개를 리스트로 반환
# most_borrowed는 [('책 제목', 횟수)] 형태
most_title = most_borrowed[0][0]
most_count = most_borrowed[0][1]
print(f"**가장 많이 대여된 책:** '{most_title}' ({most_count}회)")
else:
print("아직 대여 기록이 없어 통계를 계산할 수 없습니다.")
print("-" * 30)
def main():
"""메인 메뉴 시스템을 구동하는 함수"""
# 4단계: 프로그램 시작 시 데이터 불러오기
# 초기 데이터를 Book 객체로 변환하는 과정은 load_data() 내에서 처리됩니다.
load_data()
# 만약 파일이 없어 초기 데이터를 사용했다면, books 리스트가 비어있을 수 있으므로 다시 채웁니다.
# (실습의 일관성을 위해 1단계에서 이미 채웠다면 이 부분은 건너뛰어도 됩니다.)
if not books:
for data in initial_books_data:
books.append(Book(data["title"], data["author"], data["isbn"], data["stock"]))
while True:
print("\n*** 📖 도서 관리 시스템 메뉴 📖 ***")
print("1. 도서 목록 보기")
print("2. 대여하기")
print("3. 반납하기")
print("4. 대여 기록 조회")
print("5. 통계 보기")
print("6. 종료")
print("-" * 30)
choice = input("메뉴를 선택하세요 (1-6): ").strip()
try:
if choice == '1':
display_books()
elif choice == '2':
# 대여
title = input("대여할 책 제목을 입력하세요: ").strip()
borrowed_book = borrow_book(title)
if borrowed_book:
record_borrow(borrowed_book) # 대여 성공 시 기록 추가
elif choice == '3':
# 반납
title = input("반납할 책 제목을 입력하세요: ").strip()
return_book(title)
elif choice == '4':
# 대여 기록 조회
select_borrow()
elif choice == '5':
# 통계 보기
show_statistics()
elif choice == '6':
# 종료 시 파일 저장
print("\n프로그램을 종료합니다. 현재 데이터를 저장합니다.")
save_data()
break
else:
# 숫자 입력 잘못시 예외처리 (여기서는 범위 외의 문자를 포함하여 처리)
print("⚠️ **오류:** 1부터 6 사이의 유효한 메뉴 번호를 입력해 주세요.")
except ValueError as e:
# 존재하지 않는 책 제목 입력 등 사용자 입력 관련 예외 처리
print(f"❌ **사용자 입력 오류:** {e}")
except Exception as e:
# 기타 예상치 못한 오류 처리
print(f"❌ **시스템 오류 발생:** {e}")
# 프로그램 실행
if __name__ == "__main__":
main()
추가 도전 과제!
내가 작성한 프롬프트
자 그럼 여기서 요구사항이 더 추가 될거야.
1. 회원관리 기능 - 대여자 정보를 저장및 관리하는 기능
2. 회원 정보에 대한 클래스 Member 정의
3. 회원 등록, 조회, 목록 기능
4. 회원 기반 대여 및 반납기능
5. 회원 정보 기반 대여 기록 관리하기
6. * 연체 확인: 대여일로부터 7일 이상 지난 책 찾기
7. * 검색 기능: 저자나 제목으로 책 검색
제미나이가 만든 결과물
import datetime
import os
from collections import Counter
import random # 임시로 회원 ID를 생성하기 위해 사용
# --- 파일 이름 정의 ---
BORROW_RECORDS_FILE = "borrow_records.txt"
BOOK_STOCK_FILE = "book_stock.txt"
MEMBER_RECORDS_FILE = "member_records.txt"
# --- 글로벌 데이터 구조 ---
books = [] # Book 객체 리스트
borrow_records = [] # 대여 기록 리스트 (딕셔너리)
members = [] # Member 객체 리스트
next_member_id = 1 # 다음 회원 ID 생성을 위한 전역 변수
# --- 1. 클래스 정의 ---
class Book:
"""책 정보를 담는 클래스"""
def __init__(self, title, author, isbn, stock):
self.title = title
self.author = author
self.isbn = isbn
self.stock = int(stock)
def __str__(self):
return f"제목: {self.title}, 저자: {self.author}, ISBN: {self.isbn}, 재고: {self.stock}권"
class Member:
"""회원 정보를 담는 클래스"""
def __init__(self, member_id, name, contact):
self.member_id = int(member_id)
self.name = name
self.contact = contact
def __str__(self):
return f"ID: {self.member_id:04d}, 이름: {self.name}, 연락처: {self.contact}"
# 초기 도서 목록 데이터 (이 데이터는 파일이 없을 경우에만 사용됩니다.)
initial_books_data = [
{"title": "파이썬 완전정복", "author": "김코딩", "isbn": "9781234567890", "stock": 3},
{"title": "데이터 과학 입문", "author": "이데이터", "isbn": "9781234567891", "stock": 2},
{"title": "웹 개발의 모든 것", "author": "박웹개", "isbn": "9781234567892", "stock": 5},
{"title": "AI 프로그래밍", "author": "최인공", "isbn": "9781234567893", "stock": 1},
{"title": "알고리즘 기초", "author": "정알고", "isbn": "9781234567894", "stock": 4},
]
# --- 2. 헬퍼 함수 (조회용) ---
def get_member_by_id(member_id):
"""ID로 Member 객체를 찾는 헬퍼 함수"""
try:
return next(m for m in members if m.member_id == int(member_id))
except StopIteration:
return None
def get_book_by_title(title):
"""제목으로 Book 객체를 찾는 헬퍼 함수"""
try:
return next(book for book in books if book.title == title)
except StopIteration:
return None
# --- 3. 회원 관리 기능 ---
def register_member():
"""새로운 회원을 등록하는 기능 (메뉴 7)"""
global next_member_id
print("\n👤 **새 회원 등록** 👤")
name = input("회원 이름: ").strip()
contact = input("연락처 (예: 010-1234-5678): ").strip()
if not name or not contact:
print("❌ 이름과 연락처는 필수 입력 사항입니다.")
return
# ID 할당 및 객체 생성
new_member = Member(next_member_id, name, contact)
members.append(new_member)
next_member_id += 1
print(f"✅ 회원 등록 성공! {new_member}")
def display_members():
"""전체 회원 목록을 출력하는 기능 (메뉴 8)"""
if not members:
print("\n👥 **등록된 회원이 없습니다.**")
return
print("\n👥 **전체 회원 목록** 👥")
print("-" * 50)
for member in members:
print(member)
print("-" * 50)
# --- 4. 도서 및 회원 조회 기능 ---
def display_books():
"""현재 도서 목록 정보를 출력하는 함수 (메뉴 1)"""
if not books:
print("도서 목록이 비어 있습니다.")
return
print("\n📚 **현재 도서 목록** 📚")
print("-" * 70)
for i, book in enumerate(books, 1):
print(f"{i}. {book}")
print("-" * 70)
def search_books():
"""저자나 제목으로 책을 검색하는 기능 (메뉴 9)"""
print("\n🔍 **책 검색** 🔍")
print("1. 제목으로 검색 | 2. 저자로 검색")
search_type_choice = input("검색 방법을 선택하세요 (1 또는 2): ").strip()
keyword = input("검색할 키워드를 입력하세요: ").strip().lower()
if not keyword:
print("❌ 검색 키워드를 입력해야 합니다.")
return
found_books = []
if search_type_choice == '1':
# 제목 검색
found_books = [book for book in books if keyword in book.title.lower()]
search_label = "제목"
elif search_type_choice == '2':
# 저자 검색
found_books = [book for book in books if keyword in book.author.lower()]
search_label = "저자"
else:
print("⚠️ 잘못된 검색 방법 선택입니다.")
return
if found_books:
print(f"\n✅ '{keyword}' ({search_label}) 검색 결과:")
print("-" * 70)
for i, book in enumerate(found_books, 1):
print(f"{i}. {book}")
print("-" * 70)
else:
print(f"🔎 '{keyword}' 키워드로는 검색 결과가 없습니다.")
# --- 5. 회원 기반 대여 및 반납 기능 ---
def record_borrow(book, member_id):
"""대여 성공 시 대여 기록 리스트에 정보를 추가하는 함수"""
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
record = {
"title": book.title,
"isbn": book.isbn,
"borrow_time": now,
"member_id": member_id # 회원 ID 추가
}
borrow_records.append(record)
print(f"⭐ 대여 기록이 임시 저장되었습니다. (회원 ID: {member_id}, 시각: {now})")
def borrow_book():
"""책 제목과 회원 ID를 받아 책을 대여하는 함수 (메뉴 2)"""
print("\n➡️ **도서 대여** ➡️")
title = input("대여할 책 제목을 입력하세요: ").strip()
member_input = input("회원 ID를 입력하세요: ").strip()
try:
member_id = int(member_input)
except ValueError:
print("❌ 회원 ID는 숫자만 입력 가능합니다.")
return
book_to_borrow = get_book_by_title(title)
member_info = get_member_by_id(member_id)
if not book_to_borrow:
print(f"❌ '{title}' 책은 도서 목록에 존재하지 않습니다.")
return
if not member_info:
print(f"❌ 회원 ID '{member_id}'를 가진 회원이 존재하지 않습니다.")
return
if book_to_borrow.stock > 0:
book_to_borrow.stock -= 1
print(f"✅ '{title}' 책이 회원 {member_info.name}(ID: {member_id})님에게 대여되었습니다.")
record_borrow(book_to_borrow, member_id)
else:
print(f"❌ '{title}' 책은 현재 **대출 불가**합니다. (재고: 0)")
def return_book():
"""책 제목과 회원 ID를 받아 책을 반납하는 함수 (메뉴 3)"""
print("\n⬅️ **도서 반납** ⬅️")
title = input("반납할 책 제목을 입력하세요: ").strip()
member_input = input("반납하는 회원 ID를 입력하세요: ").strip()
try:
member_id = int(member_input)
except ValueError:
print("❌ 회원 ID는 숫자만 입력 가능합니다.")
return
book_to_return = get_book_by_title(title)
member_info = get_member_by_id(member_id)
if not book_to_return:
print(f"❌ '{title}' 책은 도서 목록에 존재하지 않습니다.")
return
# 대여 기록에서 해당 회원이 해당 책을 대여했는지 확인
matching_records = [
record for record in borrow_records
if record['title'] == title and int(record['member_id']) == member_id
]
if not matching_records:
print(f"❌ 회원 ID {member_id}는 '{title}' 책을 대여한 기록이 없습니다.")
return
# 가장 최근 기록 하나만 삭제 (반납 처리)
borrow_records.remove(matching_records[-1])
book_to_return.stock += 1
print(f"✅ '{title}' 책이 반납되었습니다. (회원 ID: {member_id}, 현재 재고: {book_to_return.stock}권)")
def select_borrow():
"""회원 정보 기반 대여 기록을 조회하고 출력하는 함수 (메뉴 4)"""
if not borrow_records:
print("\n📝 **현재 대여 기록이 없습니다.**")
return
print("\n📝 **전체 도서 대여 기록** 📝")
print("-" * 80)
for i, record in enumerate(borrow_records, 1):
member_info = get_member_by_id(record['member_id'])
member_name = member_info.name if member_info else "알 수 없는 회원"
print(f"{i:2d}. 제목: {record['title']:20s} | 대여 시각: {record['borrow_time']} | 회원: {member_name} (ID: {record['member_id']:04d})")
print("-" * 80)
def check_overdue():
"""대여일로부터 7일 이상 지난 연체된 책을 찾는 기능 (메뉴 6)"""
print("\n🚨 **도서 연체 확인 (7일 초과)** 🚨")
overdue_limit = datetime.timedelta(days=7)
now = datetime.datetime.now()
overdue_found = False
print("-" * 80)
for record in borrow_records:
try:
# 문자열 시간을 datetime 객체로 변환
borrow_time = datetime.datetime.strptime(record['borrow_time'], "%Y-%m-%d %H:%M:%S")
time_difference = now - borrow_time
if time_difference > overdue_limit:
member_info = get_member_by_id(record['member_id'])
member_name = member_info.name if member_info else "알 수 없는 회원"
print(f"🔥 연체! ({time_difference.days}일 초과)")
print(f" > 책: {record['title']}")
print(f" > 회원: {member_name} (ID: {record['member_id']:04d})")
print(f" > 대여일: {record['borrow_time']}")
overdue_found = True
except ValueError:
print(f"⚠️ 기록 날짜 형식 오류: {record['borrow_time']}")
if not overdue_found:
print("✅ 현재 연체된 도서가 없습니다.")
print("-" * 80)
# --- 6. 파일 I/O 및 데이터 초기화 ---
def initialize_default_books():
"""초기 도서 목록 데이터를 books 리스트에 채워 넣는 함수"""
global books
books.clear()
for data in initial_books_data:
books.append(Book(data["title"], data["author"], data["isbn"], data["stock"]))
print("📢 초기 도서 목록 데이터 로드 완료.")
def save_data():
"""대여 기록, 재고 상태, 회원 정보를 파일로 저장하는 함수 (메뉴 10 종료 시)"""
print("\n💾 **데이터 저장 중...**")
# 1. 책 재고 상태 저장 (title, author, isbn, stock)
try:
with open(BOOK_STOCK_FILE, 'w', encoding='utf-8') as f:
for book in books:
line = f"{book.title},{book.author},{book.isbn},{book.stock}\n"
f.write(line)
print(f"✅ 책 재고 상태가 '{BOOK_STOCK_FILE}'에 저장되었습니다.")
except Exception as e:
print(f"❌ 책 재고 저장 중 오류 발생: {e}")
# 2. 대여 기록 저장 (title, isbn, borrow_time, member_id)
try:
with open(BORROW_RECORDS_FILE, 'w', encoding='utf-8') as f:
for record in borrow_records:
line = f"{record['title']},{record['isbn']},{record['borrow_time']},{record['member_id']}\n"
f.write(line)
print(f"✅ 대여 기록이 '{BORROW_RECORDS_FILE}'에 저장되었습니다.")
except Exception as e:
print(f"❌ 대여 기록 저장 중 오류 발생: {e}")
# 3. 회원 정보 저장 (member_id, name, contact)
try:
with open(MEMBER_RECORDS_FILE, 'w', encoding='utf-8') as f:
for member in members:
line = f"{member.member_id},{member.name},{member.contact}\n"
f.write(line)
print(f"✅ 회원 정보가 '{MEMBER_RECORDS_FILE}'에 저장되었습니다.")
except Exception as e:
print(f"❌ 회원 정보 저장 중 오류 발생: {e}")
def load_data():
"""파일에서 모든 데이터를 불러오는 함수"""
print("\n📥 **데이터 불러오는 중...**")
global books
global borrow_records
global members
global next_member_id
# 1. 책 재고 상태 불러오기
if os.path.exists(BOOK_STOCK_FILE):
try:
books.clear()
with open(BOOK_STOCK_FILE, 'r', encoding='utf-8') as f:
for line in f:
# 데이터 로드 시 필드가 4개인지 확인
parts = line.strip().split(',')
if len(parts) == 4:
title, author, isbn, stock = parts
books.append(Book(title, author, isbn, int(stock)))
print(f"✅ 책 재고 상태를 '{BOOK_STOCK_FILE}'에서 불러왔습니다. ({len(books)}권)")
except Exception as e:
print(f"❌ 책 재고 불러오기 중 오류 발생: {e}. 초기 데이터를 로드합니다.")
initialize_default_books()
else:
print(f"⚠️ '{BOOK_STOCK_FILE}' 파일이 없어 초기 데이터를 사용합니다.")
initialize_default_books()
# 2. 회원 정보 불러오기
if os.path.exists(MEMBER_RECORDS_FILE):
try:
members.clear()
max_id = 0
with open(MEMBER_RECORDS_FILE, 'r', encoding='utf-8') as f:
for line in f:
parts = line.strip().split(',')
if len(parts) == 3:
member_id, name, contact = parts
member_id = int(member_id)
members.append(Member(member_id, name, contact))
if member_id > max_id:
max_id = member_id
next_member_id = max_id + 1 # 다음 ID 갱신
print(f"✅ 회원 정보를 '{MEMBER_RECORDS_FILE}'에서 불러왔습니다. ({len(members)}명)")
except Exception as e:
print(f"❌ 회원 정보 불러오기 중 오류 발생: {e}. 새로운 회원 기록을 시작합니다.")
next_member_id = 1
else:
print(f"⚠️ '{MEMBER_RECORDS_FILE}' 파일이 없어 새로운 회원 기록을 시작합니다.")
# 3. 대여 기록 불러오기
if os.path.exists(BORROW_RECORDS_FILE):
try:
borrow_records.clear()
with open(BORROW_RECORDS_FILE, 'r', encoding='utf-8') as f:
for line in f:
parts = line.strip().split(',')
if len(parts) == 4: # 새로운 형식 (title, isbn, borrow_time, member_id)
title, isbn, borrow_time, member_id = parts
borrow_records.append({
"title": title,
"isbn": isbn,
"borrow_time": borrow_time,
"member_id": int(member_id)
})
print(f"✅ 대여 기록을 '{BORROW_RECORDS_FILE}'에서 불러왔습니다. ({len(borrow_records)}개)")
except Exception as e:
print(f"❌ 대여 기록 불러오기 중 오류 발생: {e}. 새로운 대여 기록을 시작합니다.")
else:
print(f"⚠️ '{BORROW_RECORDS_FILE}' 파일이 없어 새로운 대여 기록을 시작합니다.")
# --- 7. 메인 메뉴 시스템 ---
def show_statistics():
"""총 대여 횟수 및 가장 많이 대여된 책을 출력하는 함수 (메뉴 5)"""
print("\n📊 **도서관 통계 정보** 📊")
print("-" * 30)
# 1. 총 대여 횟수
total_borrows = len(borrow_records)
print(f"**총 대여 횟수:** {total_borrows}회")
# 2. 가장 많이 대여된 책 (제목만)
if borrow_records:
titles = [record['title'] for record in borrow_records]
borrow_counts = Counter(titles)
most_borrowed = borrow_counts.most_common(1)
most_title = most_borrowed[0][0]
most_count = most_borrowed[0][1]
print(f"**가장 많이 대여된 책:** '{most_title}' ({most_count}회)")
else:
print("아직 대여 기록이 없어 통계를 계산할 수 없습니다.")
print("-" * 30)
def main():
"""메인 메뉴 시스템을 구동하는 함수"""
load_data()
while True:
print("\n*** 📖 도서 관리 시스템 메뉴 📖 ***")
print("---------------------------------")
print(" 1. 도서 목록 보기")
print(" 2. 도서 대여하기 (회원 필요)")
print(" 3. 도서 반납하기 (회원 필요)")
print(" 4. 전체 대여 기록 조회")
print(" 5. 통계 보기")
print(" 6. 연체 도서 확인 (7일 초과)")
print(" 7. 새 회원 등록")
print(" 8. 전체 회원 목록 보기")
print(" 9. 책 검색 (제목/저자)")
print(" 10. 프로그램 종료")
print("---------------------------------")
choice = input("메뉴를 선택하세요 (1-10): ").strip()
try:
if choice == '1':
display_books()
elif choice == '2':
borrow_book()
elif choice == '3':
return_book()
elif choice == '4':
select_borrow()
elif choice == '5':
show_statistics()
elif choice == '6':
check_overdue() # 신규 기능: 연체 확인
elif choice == '7':
register_member() # 신규 기능: 회원 등록
elif choice == '8':
display_members() # 신규 기능: 회원 목록 보기
elif choice == '9':
search_books() # 신규 기능: 책 검색
elif choice == '10':
print("\n프로그램을 종료합니다. 현재 데이터를 저장합니다.")
save_data()
break
else:
print("⚠️ **오류:** 1부터 10 사이의 유효한 메뉴 번호를 입력해 주세요.")
except ValueError as e:
print(f"❌ **사용자 입력 오류:** {e}")
except Exception as e:
print(f"❌ **시스템 오류 발생:** {e}")
# 프로그램 실행
if __name__ == "__main__":
main()