Project: Library Management System
This is the capstone project for Phase 4. It ties together everything from the last few lessons — classes, inheritance, encapsulation, magic methods, and file operations — to build a truly usable library management system. Completing this project means you've truly mastered Python object-oriented programming.
Project Requirements
1. Book Management (CRUD)
├── Add book (title, author, ISBN, publication year)
├── Delete book (by ISBN)
├── Modify book info
└── Search books (fuzzy search by title / search by author)
2. Borrowing Management
├── Borrow book (record borrower and borrow date)
├── Return book (check for overdue)
└── View borrowing records
3. Data Statistics
├── Total books
├── Available/borrowed count
└── Count by author
4. Data Persistence (CSV file, auto-load on startup)
1. Complete Code
Example: Library Management System
import csv
import os
from datetime import datetime, timedelta
from dataclasses import dataclass
# ====== Data Model ======
@dataclass
class Book:
"""Book data class"""
title: str
author: str
isbn: str
year: int
is_borrowed: bool = False
borrower: str = ""
borrow_date: str = ""
def to_csv_row(self):
"""Convert to CSV row"""
return [self.title, self.author, self.isbn, str(self.year),
str(self.is_borrowed), self.borrower, self.borrow_date]
@staticmethod
def from_csv_row(row):
"""Create Book object from CSV row"""
return Book(
title=row[0], author=row[1], isbn=row[2],
year=int(row[3]),
is_borrowed=row[4] == "True",
borrower=row[5], borrow_date=row[6]
)
# ====== Business Logic Layer ======
class Library:
"""Library management system"""
DATA_FILE = "library_data.csv"
def __init__(self):
self.books = []
self.load_data()
# ---- Persistence ----
def load_data(self):
"""Load data from CSV"""
if not os.path.exists(self.DATA_FILE):
return
with open(self.DATA_FILE, "r", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader, None) # Skip header
for row in reader:
if row:
self.books.append(Book.from_csv_row(row))
print(f"Loaded {len(self.books)} books.")
def save_data(self):
"""Save data to CSV"""
with open(self.DATA_FILE, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Title", "Author", "ISBN", "Year",
"Borrowed", "Borrower", "Borrow Date"])
for book in self.books:
writer.writerow(book.to_csv_row())
# ---- Book Management ----
def add_book(self, title, author, isbn, year):
"""Add a book"""
for book in self.books:
if book.isbn == isbn:
return False, "ISBN already exists!"
self.books.append(Book(title, author, isbn, year))
self.save_data()
return True, f"Added '{title}'"
def delete_book(self, isbn):
"""Delete a book"""
for i, book in enumerate(self.books):
if book.isbn == isbn:
removed = self.books.pop(i)
self.save_data()
return True, f"Deleted '{removed.title}'"
return False, "Book with this ISBN not found."
def search_by_title(self, keyword):
"""Fuzzy search by title"""
return [b for b in self.books if keyword.lower() in b.title.lower()]
def search_by_author(self, author):
"""Search by author"""
return [b for b in self.books if author.lower() in b.author.lower()]
def get_book_by_isbn(self, isbn):
"""Find book by ISBN"""
for book in self.books:
if book.isbn == isbn:
return book
return None
# ---- Borrowing Management ----
def borrow_book(self, isbn, borrower):
"""Borrow a book"""
book = self.get_book_by_isbn(isbn)
if not book:
return False, "Book not found."
if book.is_borrowed:
return False, f"'{book.title}' is already borrowed by {book.borrower}."
book.is_borrowed = True
book.borrower = borrower
book.borrow_date = datetime.now().strftime("%Y-%m-%d")
self.save_data()
return True, f"'{book.title}' has been borrowed by {borrower}."
def return_book(self, isbn):
"""Return a book"""
book = self.get_book_by_isbn(isbn)
if not book:
return False, "Book not found."
if not book.is_borrowed:
return False, f"'{book.title}' is not currently borrowed."
# Check for overdue (30-day borrowing period)
borrow_date = datetime.strptime(book.borrow_date, "%Y-%m-%d")
days_borrowed = (datetime.now() - borrow_date).days
is_overdue = days_borrowed > 30
book.is_borrowed = False
book.borrower = ""
book.borrow_date = ""
self.save_data()
if is_overdue:
return True, f"'{book.title}' returned. Overdue by {days_borrowed - 30} days."
return True, f"'{book.title}' returned. Borrowed for {days_borrowed} days."
# ---- Statistics ----
def get_stats(self):
"""Get statistics"""
total = len(self.books)
borrowed = sum(1 for b in self.books if b.is_borrowed)
available = total - borrowed
# Count by author
author_count = {}
for book in self.books:
author_count[book.author] = author_count.get(book.author, 0) + 1
return {
"total": total,
"available": available,
"borrowed": borrowed,
"by_author": dict(sorted(author_count.items(),
key=lambda x: x[1], reverse=True))
}
# ====== Presentation Layer ======
def show_menu():
print("\n" + "=" * 40)
print("Library Management System")
print("=" * 40)
print("1. Add Book")
print("2. Delete Book")
print("3. Search Books")
print("4. Display All Books")
print("5. Borrow Book")
print("6. Return Book")
print("7. Statistics")
print("8. Exit")
print("=" * 40)
def display_books(books, title="Search Results"):
"""Display a list of books"""
if not books:
print(f"\n{title}: No records.")
return
print(f"\n--- {title} ---")
print(f"{'Title':<20}{'Author':<12}{'ISBN':<15}{'Status':<8}")
print("-" * 55)
for b in books:
status = "Borrowed" if b.is_borrowed else "Available"
print(f"{b.title:<20}{b.author:<12}{b.isbn:<15}{status:<8}")
def main():
library = Library()
while True:
show_menu()
choice = input("Select option (1-8): ").strip()
if choice == "1":
title = input("Title: ").strip()
author = input("Author: ").strip()
isbn = input("ISBN: ").strip()
year = input("Year: ").strip()
success, msg = library.add_book(title, author, isbn, int(year))
print(f" {'OK' if success else 'Error'}: {msg}")
elif choice == "2":
isbn = input("Enter ISBN to delete: ").strip()
success, msg = library.delete_book(isbn)
print(f" {'OK' if success else 'Error'}: {msg}")
elif choice == "3":
print(" 1. Search by Title")
print(" 2. Search by Author")
opt = input("Choose: ").strip()
if opt == "1":
keyword = input("Enter title keyword: ").strip()
results = library.search_by_title(keyword)
elif opt == "2":
author = input("Enter author name: ").strip()
results = library.search_by_author(author)
else:
print("Invalid choice")
continue
display_books(results)
elif choice == "4":
display_books(library.books, "All Books")
elif choice == "5":
isbn = input("Enter ISBN to borrow: ").strip()
borrower = input("Borrower name: ").strip()
success, msg = library.borrow_book(isbn, borrower)
print(f" {'OK' if success else 'Error'}: {msg}")
elif choice == "6":
isbn = input("Enter ISBN to return: ").strip()
success, msg = library.return_book(isbn)
print(f" {'OK' if success else 'Error'}: {msg}")
elif choice == "7":
stats = library.get_stats()
print(f"\n--- Statistics ---")
print(f"Total books: {stats['total']}")
print(f"Available: {stats['available']} Borrowed: {stats['borrowed']}")
print(f"\nBy Author:")
for author, count in stats['by_author'].items():
print(f" {author}: {count} books")
elif choice == "8":
print("Thank you for using! Goodbye!")
break
else:
print("Invalid choice. Please enter 1-8.")
if __name__ == "__main__":
main()
2. Design Highlights
Layered architecture: Data model (Book), business logic (Library), and presentation layer (main) are separated, with clear responsibilities for each layer.
Data persistence: CSV file storage with auto-load on startup — data survives program restarts.
Borrowing logic: Automatically calculates borrow duration; warns about overdue if exceeding 30 days.
@dataclass simplification: The Book class uses the @dataclass decorator, eliminating a lot of boilerplate code.
3. Extension Directions
| Feature | Implementation Idea |
|---|---|
| User system | Add a User class, track borrowing history, limit maximum borrow count |
| Overdue fines | Calculate fines in return_book |
| GUI | Use tkinter or a web framework for a graphical interface |
| Database storage | Replace CSV with SQLite database |
| Enhanced search | Support multi-condition combined search (author + year range) |
FAQ
@dataclass and manually writing __init__?@dataclass auto-generates __init__, __repr__, __eq__, and other methods, saving you from writing boilerplate code. Functionally identical, just more concise. When a class mainly stores data without complex logic, prefer @dataclass.Enum to define states, avoiding typos in strings. However, enums are an advanced topic; strings work fine here.Summary
- The library management system uses a three-layer architecture: Data Model -> Business Logic -> Presentation Layer
@dataclasssimplifies data class definitions;to_csv_row()/from_csv_row()implement serialization- CSV files provide data persistence;
load_data()reads on startup,save_data()saves after each modification - The
datetimemodule calculates borrow duration and auto-detects overdue - List comprehensions and the dictionary
get()method are used for statistics
Exercises
-
Beginner (Difficulty: Star): Add a
categoryfield to theBookclass (Fiction, Science, History, etc.) and allow the user to select a category when adding a book. -
Intermediate (Difficulty: Star-Star): Add a "borrowing history" feature — each time a book is returned, append the borrowing record to a
history.csvfile. Record format: book title, borrower, borrow date, return date, overdue status. -
Advanced (Difficulty: Star-Star-Star): Without referring to the code above, build a "Membership Management System" from scratch. Features include: add member (name, phone, points), redeem points, view member list, sort by points, save data to CSV. Organize your code using classes.



