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

TEXT
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

PYTHON
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()
▶ Try it Yourself

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

Q What's the difference between @dataclass and manually writing __init__?
A @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.
Q Why use CSV instead of JSON for data storage?
A Book data is "tabular" (one row per book, fixed fields) — CSV fits this structure well and can be opened directly in Excel. JSON is better suited for nested structures (like student scores with sub-dictionaries). Both work; choose based on data structure.
Q Could using strings like "Borrowed"/"Available" for status cause issues?
A It's sufficient for small projects. A more rigorous approach would use an Enum to define states, avoiding typos in strings. However, enums are an advanced topic; strings work fine here.

Summary


Exercises

  1. Beginner (Difficulty: Star): Add a category field to the Book class (Fiction, Science, History, etc.) and allow the user to select a category when adding a book.

  2. Intermediate (Difficulty: Star-Star): Add a "borrowing history" feature — each time a book is returned, append the borrowing record to a history.csv file. Record format: book title, borrower, borrow date, return date, overdue status.

  3. 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.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏