+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 163 of 343

๐Ÿ“˜ Class Documentation: Docstrings and Help

Master class documentation with docstrings and help in Python. Learn best practices, documentation styles, and how to create self-documenting code ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Understanding of Python classes and methods ๐Ÿ“
  • Basic OOP concepts (classes, inheritance) ๐Ÿ
  • Familiarity with Python functions and modules ๐Ÿ’ป

What you'll learn

  • Write comprehensive class docstrings ๐ŸŽฏ
  • Document methods and attributes effectively ๐Ÿ—๏ธ
  • Generate documentation from code ๐Ÿ›
  • Create self-documenting, maintainable code โœจ

๐ŸŽฏ Introduction

Welcome to this exciting tutorial on class documentation in Python! ๐ŸŽ‰ In this guide, weโ€™ll explore how to write clear, helpful documentation for your classes using docstrings and Pythonโ€™s built-in help system.

Youโ€™ll discover how proper documentation can transform your code from a mysterious black box ๐Ÿ“ฆ into a well-organized, self-explanatory masterpiece ๐ŸŽจ. Whether youโ€™re building APIs ๐ŸŒ, libraries ๐Ÿ“š, or collaborative projects ๐Ÿ‘ฅ, understanding class documentation is essential for writing professional, maintainable code.

By the end of this tutorial, youโ€™ll be writing documentation that makes your future self (and your teammates) thank you! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Class Documentation

๐Ÿค” What is Class Documentation?

Class documentation is like a user manual for your code ๐Ÿ“–. Think of it as leaving helpful notes for anyone who uses your classes (including future you!). Itโ€™s like having a friendly guide who explains what everything does and how to use it properly.

In Python terms, documentation primarily consists of:

  • โœจ Docstrings: Special strings that describe classes, methods, and modules
  • ๐Ÿš€ Type hints: Optional type annotations that clarify expected types
  • ๐Ÿ›ก๏ธ Comments: Inline explanations for complex logic
  • ๐Ÿ“‹ Help system: Built-in tools to access documentation

๐Ÿ’ก Why Use Class Documentation?

Hereโ€™s why developers love well-documented code:

  1. Self-Documenting Code ๐Ÿ”’: Your code explains itself
  2. Better IDE Support ๐Ÿ’ป: Autocomplete shows your documentation
  3. Team Collaboration ๐Ÿ‘ฅ: Everyone understands the codebase
  4. API Generation ๐Ÿ“–: Tools can create docs from your code
  5. Maintenance ๐Ÿ”ง: Easier to update and fix bugs later

Real-world example: Imagine joining a new team and finding classes with no documentation ๐Ÿ˜ฑ. With proper docstrings, youโ€™d understand the codebase in minutes instead of hours!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Class Docstring

Letโ€™s start with a friendly example:

# ๐Ÿ‘‹ Hello, Documentation!
class BankAccount:
    """
    ๐Ÿฆ A simple bank account class for managing money.
    
    This class represents a bank account with basic operations
    like deposits, withdrawals, and balance checking.
    
    Attributes:
        account_number (str): Unique account identifier
        owner (str): Name of the account owner
        balance (float): Current account balance in dollars
    
    Example:
        >>> account = BankAccount("123456", "Alice Smith")
        >>> account.deposit(100)
        >>> account.get_balance()
        100.0
    """
    
    def __init__(self, account_number: str, owner: str):
        """
        Initialize a new bank account.
        
        Args:
            account_number: Unique account identifier
            owner: Name of the account owner
        """
        self.account_number = account_number
        self.owner = owner
        self.balance = 0.0

# ๐ŸŽฏ Access the documentation
print(BankAccount.__doc__)
help(BankAccount)

๐Ÿ’ก Explanation: The triple-quoted string right after the class definition is the docstring. Notice how we include a description, attributes, and examples!

๐ŸŽฏ Method Documentation

Hereโ€™s how to document methods effectively:

class ShoppingCart:
    """๐Ÿ›’ A shopping cart for an e-commerce application."""
    
    def add_item(self, item: str, price: float, quantity: int = 1) -> None:
        """
        โž• Add an item to the shopping cart.
        
        Args:
            item: Name of the product to add
            price: Price per unit in dollars
            quantity: Number of items to add (default: 1)
            
        Raises:
            ValueError: If price is negative or quantity is less than 1
            
        Example:
            >>> cart = ShoppingCart()
            >>> cart.add_item("Python Book", 29.99, 2)
            >>> cart.add_item("Coffee โ˜•", 4.99)
        """
        if price < 0:
            raise ValueError("Price cannot be negative! ๐Ÿ’ธ")
        if quantity < 1:
            raise ValueError("Quantity must be at least 1! ๐Ÿ“ฆ")
            
        # Implementation here...
        
    def calculate_total(self) -> float:
        """
        ๐Ÿ’ฐ Calculate the total price of all items in the cart.
        
        Returns:
            float: Total price including all items and quantities
            
        Note:
            This method does not include tax or shipping costs.
        """
        # Implementation here...
        return 0.0

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: Game Character Class

Letโ€™s build a well-documented game character class:

class GameCharacter:
    """
    ๐ŸŽฎ A character class for an RPG game.
    
    This class represents a playable character with attributes,
    abilities, and inventory management.
    
    Attributes:
        name (str): Character's display name
        level (int): Current experience level (1-100)
        health (int): Current health points
        max_health (int): Maximum health points
        abilities (list): List of learned abilities
        inventory (dict): Items and their quantities
        
    Class Attributes:
        MAX_LEVEL (int): Maximum achievable level (100)
        BASE_HEALTH (int): Starting health for new characters (100)
    """
    
    MAX_LEVEL = 100
    BASE_HEALTH = 100
    
    def __init__(self, name: str, character_class: str = "Warrior"):
        """
        Create a new game character.
        
        Args:
            name: Character's display name
            character_class: Type of character (Warrior, Mage, Rogue)
                           Defaults to "Warrior"
                           
        Example:
            >>> hero = GameCharacter("Aragorn", "Warrior")
            >>> mage = GameCharacter("Gandalf", "Mage")
        """
        self.name = name
        self.character_class = character_class
        self.level = 1
        self.health = self.BASE_HEALTH
        self.max_health = self.BASE_HEALTH
        self.abilities = ["Basic Attack ๐Ÿ—ก๏ธ"]
        self.inventory = {}
        
    def level_up(self) -> None:
        """
        ๐ŸŽ‰ Level up the character, increasing stats.
        
        Effects:
            - Increases level by 1 (max: 100)
            - Increases max health by 10
            - Fully restores health
            - May unlock new abilities
            
        Raises:
            ValueError: If already at maximum level
            
        Example:
            >>> hero = GameCharacter("Link")
            >>> hero.level_up()
            >>> print(f"Level: {hero.level}, Health: {hero.max_health}")
            Level: 2, Health: 110
        """
        if self.level >= self.MAX_LEVEL:
            raise ValueError(f"Already at max level! ๐Ÿ†")
            
        self.level += 1
        self.max_health += 10
        self.health = self.max_health
        
        # Check for new abilities
        if self.level % 5 == 0:
            self.abilities.append(f"Special Move {self.level//5} โšก")
            
    def take_damage(self, damage: int) -> bool:
        """
        ๐Ÿ’” Apply damage to the character.
        
        Args:
            damage: Amount of damage to apply
            
        Returns:
            bool: True if character is still alive, False if defeated
            
        Example:
            >>> hero = GameCharacter("Hero")
            >>> hero.take_damage(30)
            True
            >>> hero.health
            70
        """
        self.health = max(0, self.health - damage)
        return self.health > 0
        
    def add_item(self, item: str, quantity: int = 1) -> None:
        """
        ๐ŸŽ’ Add an item to the character's inventory.
        
        Args:
            item: Name of the item to add
            quantity: Number of items to add (default: 1)
            
        Example:
            >>> hero = GameCharacter("Hero")
            >>> hero.add_item("Health Potion ๐Ÿงช", 3)
            >>> hero.add_item("Magic Sword โš”๏ธ")
        """
        if item in self.inventory:
            self.inventory[item] += quantity
        else:
            self.inventory[item] = quantity

# ๐ŸŽฏ Using the documentation
help(GameCharacter)
help(GameCharacter.level_up)

๐ŸŽฎ Example 2: API Client Class

Letโ€™s create a documented API client:

from typing import Dict, Optional, List
import json

class WeatherAPIClient:
    """
    ๐ŸŒค๏ธ A client for interacting with a weather API.
    
    This class provides methods to fetch weather data, forecasts,
    and historical information from a weather service.
    
    Attributes:
        api_key (str): API authentication key
        base_url (str): Base URL for the API endpoints
        timeout (int): Request timeout in seconds
        cache (dict): Simple cache for recent requests
        
    Environment Variables:
        WEATHER_API_KEY: Can be used instead of passing api_key
        
    Example:
        >>> client = WeatherAPIClient("your-api-key")
        >>> weather = client.get_current_weather("New York")
        >>> print(f"Temperature: {weather['temperature']}ยฐF")
        
    Note:
        This is a demonstration class. In production, use
        proper HTTP libraries and error handling.
    """
    
    DEFAULT_TIMEOUT = 30
    BASE_URL = "https://api.weather.example.com/v1"
    
    def __init__(self, api_key: Optional[str] = None, timeout: int = DEFAULT_TIMEOUT):
        """
        Initialize the weather API client.
        
        Args:
            api_key: API key for authentication. If not provided,
                    will check WEATHER_API_KEY environment variable
            timeout: Request timeout in seconds (default: 30)
            
        Raises:
            ValueError: If no API key is provided or found
            
        Example:
            >>> # Using direct API key
            >>> client = WeatherAPIClient("abc123")
            
            >>> # Using environment variable
            >>> import os
            >>> os.environ['WEATHER_API_KEY'] = "abc123"
            >>> client = WeatherAPIClient()
        """
        if api_key is None:
            import os
            api_key = os.environ.get('WEATHER_API_KEY')
            if not api_key:
                raise ValueError("API key required! ๐Ÿ”‘")
                
        self.api_key = api_key
        self.base_url = self.BASE_URL
        self.timeout = timeout
        self.cache: Dict[str, dict] = {}
        
    def get_current_weather(self, city: str, units: str = "fahrenheit") -> Dict:
        """
        ๐ŸŒก๏ธ Get current weather for a city.
        
        Args:
            city: Name of the city (e.g., "London", "Tokyo")
            units: Temperature units - "fahrenheit" or "celsius"
                  (default: "fahrenheit")
                  
        Returns:
            dict: Weather data containing:
                - temperature (float): Current temperature
                - description (str): Weather description
                - humidity (int): Humidity percentage
                - wind_speed (float): Wind speed in mph/kph
                
        Raises:
            ValueError: If city is not found or units are invalid
            ConnectionError: If API is unreachable
            
        Example:
            >>> client = WeatherAPIClient("api-key")
            >>> weather = client.get_current_weather("Paris", "celsius")
            >>> print(weather)
            {
                'temperature': 22.5,
                'description': 'Partly cloudy โ›…',
                'humidity': 65,
                'wind_speed': 12.3
            }
        """
        # Check cache first
        cache_key = f"{city}:{units}"
        if cache_key in self.cache:
            print(f"๐Ÿ“ฆ Returning cached data for {city}")
            return self.cache[cache_key]
            
        # Simulate API call
        weather_data = {
            'temperature': 72.5,
            'description': 'Sunny โ˜€๏ธ',
            'humidity': 45,
            'wind_speed': 8.2
        }
        
        # Cache the result
        self.cache[cache_key] = weather_data
        return weather_data
        
    def get_forecast(self, city: str, days: int = 5) -> List[Dict]:
        """
        ๐Ÿ“… Get weather forecast for multiple days.
        
        Args:
            city: Name of the city
            days: Number of days to forecast (1-14, default: 5)
            
        Returns:
            list: List of daily forecasts, each containing:
                - date (str): Date in YYYY-MM-DD format
                - high (float): High temperature
                - low (float): Low temperature
                - description (str): Weather description
                
        Raises:
            ValueError: If days is outside valid range (1-14)
            
        See Also:
            get_current_weather: For current conditions
            get_historical_weather: For past weather data
        """
        if not 1 <= days <= 14:
            raise ValueError("Days must be between 1 and 14! ๐Ÿ“†")
            
        # Simulate forecast data
        forecast = []
        for i in range(days):
            forecast.append({
                'date': f'2024-01-{i+1:02d}',
                'high': 75 + i,
                'low': 60 + i,
                'description': 'Partly cloudy โ›…'
            })
            
        return forecast
        
    @classmethod
    def from_config(cls, config_file: str) -> 'WeatherAPIClient':
        """
        ๐Ÿ”ง Create a client instance from a configuration file.
        
        Args:
            config_file: Path to JSON configuration file
            
        Returns:
            WeatherAPIClient: Configured client instance
            
        Config File Format:
            {
                "api_key": "your-key",
                "timeout": 60,
                "base_url": "https://custom.url.com"
            }
            
        Example:
            >>> client = WeatherAPIClient.from_config("config.json")
        """
        with open(config_file, 'r') as f:
            config = json.load(f)
            
        return cls(
            api_key=config.get('api_key'),
            timeout=config.get('timeout', cls.DEFAULT_TIMEOUT)
        )

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Docstring Formats

Python supports multiple docstring formats:

class DataProcessor:
    """
    ๐ŸŽฏ Advanced documentation example using different formats.
    """
    
    def google_style(self, param1: str, param2: int) -> bool:
        """
        Google style docstring example.
        
        Args:
            param1: The first parameter description
            param2: The second parameter description
            
        Returns:
            bool: Description of return value
            
        Raises:
            ValueError: If param2 is negative
            
        Note:
            This is the recommended style for most Python projects.
        """
        pass
        
    def numpy_style(self, param1: str, param2: int) -> bool:
        """
        NumPy style docstring example.
        
        Parameters
        ----------
        param1 : str
            The first parameter description
        param2 : int
            The second parameter description
            
        Returns
        -------
        bool
            Description of return value
            
        Raises
        ------
        ValueError
            If param2 is negative
            
        Notes
        -----
        This style is common in scientific Python libraries.
        """
        pass
        
    def sphinx_style(self, param1: str, param2: int) -> bool:
        """
        Sphinx style docstring example.
        
        :param param1: The first parameter description
        :type param1: str
        :param param2: The second parameter description
        :type param2: int
        :returns: Description of return value
        :rtype: bool
        :raises ValueError: If param2 is negative
        
        .. note:: This style works well with Sphinx documentation.
        """
        pass

๐Ÿ—๏ธ Property Documentation

Document properties clearly:

class Temperature:
    """๐ŸŒก๏ธ A class for temperature conversions and management."""
    
    def __init__(self, celsius: float = 0.0):
        """Initialize with temperature in Celsius."""
        self._celsius = celsius
        
    @property
    def celsius(self) -> float:
        """
        float: Temperature in Celsius.
        
        Example:
            >>> temp = Temperature(25)
            >>> temp.celsius
            25.0
        """
        return self._celsius
        
    @celsius.setter
    def celsius(self, value: float) -> None:
        """
        Set temperature in Celsius.
        
        Args:
            value: Temperature in Celsius
            
        Raises:
            ValueError: If temperature is below absolute zero (-273.15ยฐC)
        """
        if value < -273.15:
            raise ValueError("Temperature below absolute zero! ๐Ÿฅถ")
        self._celsius = value
        
    @property
    def fahrenheit(self) -> float:
        """
        float: Temperature in Fahrenheit (read-only).
        
        Calculated as: (celsius * 9/5) + 32
        
        Example:
            >>> temp = Temperature(0)
            >>> temp.fahrenheit
            32.0
        """
        return (self._celsius * 9/5) + 32
        
    @property
    def kelvin(self) -> float:
        """
        float: Temperature in Kelvin (read-only).
        
        Calculated as: celsius + 273.15
        
        Example:
            >>> temp = Temperature(0)
            >>> temp.kelvin
            273.15
        """
        return self._celsius + 273.15

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Outdated Documentation

# โŒ Wrong way - documentation doesn't match code!
class Calculator:
    """Calculator with add and subtract methods."""
    
    def multiply(self, a: float, b: float) -> float:
        """Multiply two numbers."""  # ๐Ÿ˜ฐ Docs say add/subtract!
        return a * b

# โœ… Correct way - keep docs in sync!
class Calculator:
    """
    ๐Ÿ“ฑ Calculator with basic arithmetic operations.
    
    Supports: multiply, divide, power operations
    """
    
    def multiply(self, a: float, b: float) -> float:
        """Multiply two numbers together."""
        return a * b

๐Ÿคฏ Pitfall 2: Too Much or Too Little Documentation

# โŒ Too little - what does this do?
class DataManager:
    """Manages data."""  # ๐Ÿ˜• Not helpful!
    pass

# โŒ Too much - obvious documentation
class User:
    def get_name(self) -> str:
        """
        This method gets the name of the user.
        It returns the name attribute.
        The name is a string.
        """  # ๐Ÿ™„ We can see that from the code!
        return self.name

# โœ… Just right - meaningful documentation
class DataManager:
    """
    ๐Ÿ—„๏ธ Manages application data persistence and caching.
    
    Handles database connections, query optimization,
    and in-memory caching for frequently accessed data.
    """
    
class User:
    def get_display_name(self) -> str:
        """
        Get formatted display name (e.g., "Smith, John").
        
        Returns lastname, firstname format unless user
        has set a custom display preference.
        """
        if self.custom_display:
            return self.custom_display
        return f"{self.last_name}, {self.first_name}"

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Write for Your Audience: Consider who will read the docs
  2. ๐Ÿ“ Document the Why: Explain intentions, not just what
  3. ๐Ÿ›ก๏ธ Include Examples: Show how to use your classes
  4. ๐ŸŽจ Use Consistent Style: Pick one format and stick to it
  5. โœจ Keep It Current: Update docs when code changes
  6. ๐Ÿ” Document Edge Cases: Mention special behaviors
  7. ๐Ÿ“‹ List Prerequisites: What users need to know

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Document a Library Management System

Create a well-documented library management system:

๐Ÿ“‹ Requirements:

  • โœ… Book class with title, author, ISBN, and availability
  • ๐Ÿท๏ธ Library class to manage book collection
  • ๐Ÿ‘ค Member class for library members
  • ๐Ÿ“… Loan tracking with due dates
  • ๐ŸŽจ Clear, comprehensive documentation!

๐Ÿš€ Bonus Points:

  • Add type hints for all parameters
  • Include usage examples in docstrings
  • Document edge cases and errors
  • Generate HTML documentation

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
from typing import List, Optional, Dict
from datetime import datetime, timedelta

class Book:
    """
    ๐Ÿ“š Represents a book in the library system.
    
    This class manages book information and tracks its
    availability status in the library.
    
    Attributes:
        isbn (str): International Standard Book Number
        title (str): Book title
        author (str): Primary author name
        is_available (bool): Whether book can be borrowed
        location (str): Shelf location in library
        
    Example:
        >>> book = Book("978-0-13-235088-4", "Clean Code", "Robert C. Martin")
        >>> book.is_available
        True
        >>> book.location
        'A1-Programming'
    """
    
    def __init__(self, isbn: str, title: str, author: str, location: str = "Unassigned"):
        """
        Create a new book entry.
        
        Args:
            isbn: Unique book identifier (ISBN-10 or ISBN-13)
            title: Full title of the book
            author: Primary author (lastname, firstname format preferred)
            location: Shelf location (default: "Unassigned")
            
        Raises:
            ValueError: If ISBN format is invalid
        """
        if not self._validate_isbn(isbn):
            raise ValueError(f"Invalid ISBN format: {isbn}")
            
        self.isbn = isbn
        self.title = title
        self.author = author
        self.is_available = True
        self.location = location
        
    @staticmethod
    def _validate_isbn(isbn: str) -> bool:
        """Validate ISBN format (simplified check)."""
        isbn_digits = isbn.replace("-", "")
        return len(isbn_digits) in [10, 13] and isbn_digits.isdigit()
        
    def __str__(self) -> str:
        """Return readable string representation."""
        status = "โœ… Available" if self.is_available else "โŒ Borrowed"
        return f"{self.title} by {self.author} ({status})"


class Member:
    """
    ๐Ÿ‘ค Library member who can borrow books.
    
    Tracks member information and borrowing history.
    
    Attributes:
        member_id (str): Unique member identifier
        name (str): Member's full name
        email (str): Contact email address
        join_date (datetime): When member joined
        borrowed_books (List[str]): ISBNs of currently borrowed books
        
    Class Attributes:
        MAX_BOOKS (int): Maximum books a member can borrow (5)
    """
    
    MAX_BOOKS = 5
    
    def __init__(self, member_id: str, name: str, email: str):
        """
        Register a new library member.
        
        Args:
            member_id: Unique identifier (e.g., "MEM001")
            name: Full name of the member
            email: Valid email address for notifications
        """
        self.member_id = member_id
        self.name = name
        self.email = email
        self.join_date = datetime.now()
        self.borrowed_books: List[str] = []
        
    def can_borrow(self) -> bool:
        """
        Check if member can borrow more books.
        
        Returns:
            bool: True if under borrowing limit
            
        Example:
            >>> member = Member("MEM001", "Alice Johnson", "[email protected]")
            >>> member.can_borrow()
            True
        """
        return len(self.borrowed_books) < self.MAX_BOOKS


class Library:
    """
    ๐Ÿ›๏ธ Main library management system.
    
    Manages books, members, and loans with due date tracking.
    
    Attributes:
        name (str): Library name
        books (Dict[str, Book]): Books indexed by ISBN
        members (Dict[str, Member]): Members indexed by ID
        loans (Dict[str, Dict]): Active loans with due dates
        
    Example:
        >>> library = Library("City Central Library")
        >>> library.add_book(Book("123", "Python 101", "Guido"))
        >>> library.register_member("MEM001", "John Doe", "[email protected]")
        >>> library.borrow_book("123", "MEM001")
        'Book borrowed successfully! Due: 2024-01-14'
    """
    
    LOAN_PERIOD_DAYS = 14
    
    def __init__(self, name: str):
        """
        Initialize a new library system.
        
        Args:
            name: Name of the library
        """
        self.name = name
        self.books: Dict[str, Book] = {}
        self.members: Dict[str, Member] = {}
        self.loans: Dict[str, Dict] = {}  # ISBN -> {member_id, due_date}
        
    def add_book(self, book: Book) -> None:
        """
        ๐Ÿ“– Add a book to the library collection.
        
        Args:
            book: Book instance to add
            
        Raises:
            ValueError: If book with same ISBN already exists
            
        Example:
            >>> library = Library("My Library")
            >>> book = Book("978-0-13-235088-4", "Clean Code", "Martin")
            >>> library.add_book(book)
        """
        if book.isbn in self.books:
            raise ValueError(f"Book {book.isbn} already exists! ๐Ÿ“š")
        self.books[book.isbn] = book
        
    def register_member(self, member_id: str, name: str, email: str) -> Member:
        """
        ๐Ÿ‘ฅ Register a new library member.
        
        Args:
            member_id: Unique member identifier
            name: Member's full name
            email: Contact email
            
        Returns:
            Member: The newly created member
            
        Raises:
            ValueError: If member_id already exists
        """
        if member_id in self.members:
            raise ValueError(f"Member {member_id} already exists! ๐Ÿ‘ค")
            
        member = Member(member_id, name, email)
        self.members[member_id] = member
        return member
        
    def borrow_book(self, isbn: str, member_id: str) -> str:
        """
        ๐Ÿ“š Process a book loan.
        
        Args:
            isbn: ISBN of book to borrow
            member_id: ID of borrowing member
            
        Returns:
            str: Success message with due date
            
        Raises:
            KeyError: If book or member not found
            ValueError: If book unavailable or member limit reached
            
        Example:
            >>> msg = library.borrow_book("123", "MEM001")
            >>> print(msg)
            'Book borrowed successfully! Due: 2024-01-14'
        """
        # Validate book
        if isbn not in self.books:
            raise KeyError(f"Book {isbn} not found! ๐Ÿ”")
        book = self.books[isbn]
        if not book.is_available:
            raise ValueError(f"Book '{book.title}' is not available! โŒ")
            
        # Validate member
        if member_id not in self.members:
            raise KeyError(f"Member {member_id} not found! ๐Ÿ”")
        member = self.members[member_id]
        if not member.can_borrow():
            raise ValueError(f"Member has reached borrowing limit! ๐Ÿ“š")
            
        # Process loan
        due_date = datetime.now() + timedelta(days=self.LOAN_PERIOD_DAYS)
        book.is_available = False
        member.borrowed_books.append(isbn)
        self.loans[isbn] = {
            'member_id': member_id,
            'due_date': due_date
        }
        
        return f"Book borrowed successfully! Due: {due_date.strftime('%Y-%m-%d')}"
        
    def return_book(self, isbn: str) -> str:
        """
        ๐Ÿ“ฅ Process a book return.
        
        Args:
            isbn: ISBN of book being returned
            
        Returns:
            str: Return confirmation message
            
        Raises:
            KeyError: If book not found or not on loan
        """
        if isbn not in self.loans:
            raise KeyError(f"Book {isbn} is not on loan! ๐Ÿ“–")
            
        # Process return
        loan = self.loans[isbn]
        member = self.members[loan['member_id']]
        book = self.books[isbn]
        
        book.is_available = True
        member.borrowed_books.remove(isbn)
        del self.loans[isbn]
        
        # Check if overdue
        if datetime.now() > loan['due_date']:
            return f"Book returned (was overdue)! โฐ"
        return f"Book returned on time! โœ…"

# ๐ŸŽฎ Test the documentation
if __name__ == "__main__":
    help(Library)
    help(Library.borrow_book)
    
    # Create instances
    library = Library("City Library ๐Ÿ›๏ธ")
    book = Book("978-0-13-235088-4", "Clean Code", "Robert C. Martin")
    library.add_book(book)
    
    print(book)  # Uses __str__ method

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Write comprehensive docstrings that explain your code clearly ๐Ÿ’ช
  • โœ… Document classes, methods, and properties effectively ๐Ÿ›ก๏ธ
  • โœ… Use different documentation styles (Google, NumPy, Sphinx) ๐ŸŽฏ
  • โœ… Create self-documenting code that helps users ๐Ÿ›
  • โœ… Generate documentation from your code! ๐Ÿš€

Remember: Good documentation is like a gift to your future self and your teammates! ๐ŸŽ

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered class documentation in Python!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice documenting your existing classes
  2. ๐Ÿ—๏ธ Use a tool like Sphinx to generate HTML docs
  3. ๐Ÿ“š Explore tools like pydoc and help()
  4. ๐ŸŒŸ Share well-documented code with others!

Your next tutorial will be: Testing Classes: Unit Tests - where youโ€™ll learn to write tests that verify your documented behavior!

Remember: Well-documented code is professional code. Keep documenting, keep learning, and most importantly, have fun! ๐Ÿš€


Happy documenting! ๐ŸŽ‰๐Ÿ“šโœจ