📖 Document Your Code

Good documentation explains what your code does, why it exists, and how to use it. Clear documentation makes your code easier to understand, maintain, and use by others (including your future self).

"""
shopping_cart.py - A simple shopping cart implementation

This module provides a basic shopping cart system for an e-commerce application.
It handles adding items, calculating totals, and applying discounts.

Example usage:
    cart = ShoppingCart()
    cart.add_item("laptop", 999.99, 1)
    cart.add_item("mouse", 25.50, 2)
    total = cart.get_total()
"""

from typing import List, Dict, Optional
from datetime import datetime


class ShoppingCart:
    """
    A shopping cart that manages items and calculates totals.
    
    The cart stores items with their prices and quantities, and provides
    methods to calculate totals, apply discounts, and manage the cart contents.
    
    Attributes:
        items (List[Dict]): List of items in the cart
        discount_rate (float): Discount rate as decimal (0.1 = 10%)
        
    Example:
        >>> cart = ShoppingCart()
        >>> cart.add_item("book", 19.99, 2)
        >>> cart.get_total()
        39.98
    """
    
    def __init__(self, discount_rate: float = 0.0):
        """
        Initialize a new shopping cart.
        
        Args:
            discount_rate (float): Discount rate as decimal (default: 0.0)
            
        Raises:
            ValueError: If discount_rate is negative or greater than 1.0
        """
        if discount_rate < 0 or discount_rate > 1.0:
            raise ValueError("Discount rate must be between 0.0 and 1.0")
            
        self.items: List[Dict] = []
        self.discount_rate = discount_rate
        self.created_at = datetime.now()
    
    def add_item(self, name: str, price: float, quantity: int = 1) -> None:
        """
        Add an item to the shopping cart.
        
        Args:
            name (str): Name of the item
            price (float): Price per unit
            quantity (int): Number of items to add (default: 1)
            
        Raises:
            ValueError: If price is negative or quantity is not positive
            
        Example:
            >>> cart.add_item("laptop", 999.99, 1)
            >>> len(cart.items)
            1
        """
        if price < 0:
            raise ValueError("Price cannot be negative")
        if quantity <= 0:
            raise ValueError("Quantity must be positive")
        
        # Check if item already exists in cart
        for item in self.items:
            if item['name'] == name and item['price'] == price:
                item['quantity'] += quantity
                return
        
        # Add new item to cart
        item = {
            'name': name,
            'price': price,
            'quantity': quantity,
            'added_at': datetime.now()
        }
        self.items.append(item)
    
    def remove_item(self, name: str) -> bool:
        """
        Remove an item from the cart by name.
        
        Args:
            name (str): Name of the item to remove
            
        Returns:
            bool: True if item was found and removed, False otherwise
            
        Example:
            >>> cart.add_item("book", 19.99)
            >>> cart.remove_item("book")
            True
            >>> cart.remove_item("nonexistent")
            False
        """
        for i, item in enumerate(self.items):
            if item['name'] == name:
                del self.items[i]
                return True
        return False
    
    def get_subtotal(self) -> float:
        """
        Calculate the subtotal before discount.
        
        Returns:
            float: Total price of all items before discount
            
        Example:
            >>> cart.add_item("book", 10.00, 2)
            >>> cart.get_subtotal()
            20.0
        """
        return sum(item['price'] * item['quantity'] for item in self.items)
    
    def get_total(self) -> float:
        """
        Calculate the final total after applying discount.
        
        Returns:
            float: Final total after discount, rounded to 2 decimal places
            
        Example:
            >>> cart = ShoppingCart(discount_rate=0.1)  # 10% discount
            >>> cart.add_item("item", 100.00)
            >>> cart.get_total()
            90.0
        """
        subtotal = self.get_subtotal()
        discount_amount = subtotal * self.discount_rate
        final_total = subtotal - discount_amount
        return round(final_total, 2)
    
    def get_item_count(self) -> int:
        """
        Get the total number of items in the cart.
        
        Returns:
            int: Total quantity of all items
            
        Note:
            This counts individual items, not unique products.
            For example, 3 books and 2 pens = 5 items total.
        """
        return sum(item['quantity'] for item in self.items)
    
    def is_empty(self) -> bool:
        """
        Check if the cart is empty.
        
        Returns:
            bool: True if cart has no items, False otherwise
        """
        return len(self.items) == 0
    
    def clear(self) -> None:
        """
        Remove all items from the cart.
        
        Warning:
            This action cannot be undone. All items will be permanently
            removed from the cart.
        """
        self.items.clear()


def calculate_shipping_cost(total_weight: float, distance: float) -> float:
    """
    Calculate shipping cost based on weight and distance.
    
    The shipping cost is calculated using a base rate plus weight and
    distance charges. Free shipping applies for orders over certain weight.
    
    Args:
        total_weight (float): Total weight in kg
        distance (float): Shipping distance in km
        
    Returns:
        float: Shipping cost in dollars
        
    Raises:
        ValueError: If weight or distance is negative
        
    Note:
        Free shipping applies for orders over 10kg.
        
    Example:
        >>> calculate_shipping_cost(2.5, 100)  # 2.5kg, 100km
        12.5
        >>> calculate_shipping_cost(15, 50)     # Free shipping
        0.0
    """
    if total_weight < 0 or distance < 0:
        raise ValueError("Weight and distance must be non-negative")
    
    # Free shipping for heavy orders
    if total_weight > 10:
        return 0.0
    
    # Calculate shipping cost
    base_rate = 5.00
    weight_rate = total_weight * 1.50  # $1.50 per kg
    distance_rate = distance * 0.05    # $0.05 per km
    
    total_cost = base_rate + weight_rate + distance_rate
    return round(total_cost, 2)


def format_order_summary(cart: ShoppingCart, customer_name: str) -> str:
    """
    Generate a formatted order summary for display or printing.
    
    Args:
        cart (ShoppingCart): The shopping cart to summarize
        customer_name (str): Name of the customer
        
    Returns:
        str: Formatted order summary as a multi-line string
        
    Example:
        >>> cart = ShoppingCart()
        >>> cart.add_item("Book", 19.99, 2)
        >>> summary = format_order_summary(cart, "Alice")
        >>> print(summary)
        Order Summary for Alice
        =======================
        - Book x2: $39.98
        
        Subtotal: $39.98
        Total: $39.98
    """
    # Build the summary string
    lines = [
        f"Order Summary for {customer_name}",
        "=" * (len(customer_name) + 17),  # Adjust line length
        ""
    ]
    
    # Add each item
    for item in cart.items:
        item_total = item['price'] * item['quantity']
        line = f"- {item['name']} x{item['quantity']}: ${item_total:.2f}"
        lines.append(line)
    
    # Add totals
    lines.extend([
        "",
        f"Subtotal: ${cart.get_subtotal():.2f}",
        f"Total: ${cart.get_total():.2f}"
    ])
    
    return "\n".join(lines)


# Example usage with detailed comments
def demonstrate_shopping_cart():
    """
    Demonstrate the shopping cart functionality.
    
    This function shows how to use the ShoppingCart class and related
    functions to create a complete shopping experience.
    """
    print("Shopping Cart Documentation Example")
    print("=" * 40)
    
    # Create a cart with 10% discount
    cart = ShoppingCart(discount_rate=0.10)
    
    # Add some items to the cart
    cart.add_item("Programming Book", 45.99, 1)
    cart.add_item("Wireless Mouse", 29.95, 2)
    cart.add_item("USB Cable", 12.50, 3)
    
    # Display cart information
    print(f"Items in cart: {cart.get_item_count()}")
    print(f"Subtotal: ${cart.get_subtotal():.2f}")
    print(f"Total after discount: ${cart.get_total():.2f}")
    print(f"Cart empty: {cart.is_empty()}")
    
    # Calculate shipping
    total_weight = 2.8  # kg
    shipping_distance = 150  # km
    shipping_cost = calculate_shipping_cost(total_weight, shipping_distance)
    print(f"Shipping cost: ${shipping_cost:.2f}")
    
    # Generate order summary
    summary = format_order_summary(cart, "John Doe")
    print("\n" + summary)
    
    # Remove an item
    if cart.remove_item("USB Cable"):
        print(f"\nAfter removing USB Cable:")
        print(f"New total: ${cart.get_total():.2f}")


if __name__ == "__main__":
    # This block runs only when the script is executed directly,
    # not when imported as a module
    demonstrate_shopping_cart()

🎯 Understanding Documentation

Good documentation serves different audiences and purposes in your codebase.

Docstring Conventions

"""
utilities.py - Common utility functions

This module provides helpful utility functions for data processing,
validation, and formatting that are used across the application.
"""

from typing import List, Union, Optional
import re


def validate_email(email: str) -> bool:
    """
    Validate an email address format.
    
    Checks if the email follows basic email format rules including
    having an @ symbol and a domain with at least one dot.
    
    Args:
        email (str): The email address to validate
        
    Returns:
        bool: True if email format is valid, False otherwise
        
    Example:
        >>> validate_email("user@example.com")
        True
        >>> validate_email("invalid-email")
        False
        
    Note:
        This is a basic validation. For production use, consider
        more robust email validation libraries.
    """
    if not email or not isinstance(email, str):
        return False
    
    # Basic email pattern check
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))


def format_currency(amount: float, currency: str = "USD") -> str:
    """
    Format a monetary amount as a currency string.
    
    Args:
        amount (float): The monetary amount to format
        currency (str, optional): Currency code. Defaults to "USD".
        
    Returns:
        str: Formatted currency string
        
    Raises:
        ValueError: If amount is negative or currency code is invalid
        
    Examples:
        >>> format_currency(19.99)
        '$19.99'
        >>> format_currency(1000.5, "EUR")
        '€1,000.50'
        >>> format_currency(0)
        '$0.00'
    """
    if amount < 0:
        raise ValueError("Amount cannot be negative")
    
    # Currency symbols mapping
    symbols = {
        "USD": "$",
        "EUR": "€", 
        "GBP": "£",
        "JPY": "¥"
    }
    
    if currency not in symbols:
        raise ValueError(f"Unsupported currency: {currency}")
    
    symbol = symbols[currency]
    
    # Format with thousands separator and 2 decimal places
    if amount >= 1000:
        return f"{symbol}{amount:,.2f}"
    else:
        return f"{symbol}{amount:.2f}"


def chunk_list(items: List, chunk_size: int) -> List[List]:
    """
    Split a list into smaller chunks of specified size.
    
    This is useful for processing large lists in smaller batches,
    such as when making API calls or database operations.
    
    Args:
        items (List): The list to split into chunks
        chunk_size (int): Maximum size of each chunk
        
    Returns:
        List[List]: List of chunks, where each chunk is a list
        
    Raises:
        ValueError: If chunk_size is not positive
        
    Examples:
        >>> chunk_list([1, 2, 3, 4, 5], 2)
        [[1, 2], [3, 4], [5]]
        >>> chunk_list([], 3)
        []
        >>> chunk_list([1, 2, 3], 5)
        [[1, 2, 3]]
        
    Note:
        The last chunk may contain fewer items than chunk_size
        if the total number of items is not evenly divisible.
    """
    if chunk_size <= 0:
        raise ValueError("Chunk size must be positive")
    
    if not items:
        return []
    
    chunks = []
    for i in range(0, len(items), chunk_size):
        chunk = items[i:i + chunk_size]
        chunks.append(chunk)
    
    return chunks


class DataProcessor:
    """
    A utility class for processing and transforming data.
    
    This class provides methods for common data processing tasks
    like filtering, transforming, and aggregating data sets.
    
    Attributes:
        data (List): The current dataset being processed
        processed_count (int): Number of items processed
        
    Example:
        >>> processor = DataProcessor([1, 2, 3, 4, 5])
        >>> processor.filter_even()
        >>> processor.get_data()
        [2, 4]
    """
    
    def __init__(self, data: List = None):
        """
        Initialize the data processor.
        
        Args:
            data (List, optional): Initial data to process. Defaults to empty list.
        """
        self.data = data if data is not None else []
        self.processed_count = 0
    
    def filter_even(self) -> 'DataProcessor':
        """
        Filter the data to keep only even numbers.
        
        Returns:
            DataProcessor: Self, for method chaining
            
        Raises:
            TypeError: If data contains non-numeric values
            
        Example:
            >>> processor = DataProcessor([1, 2, 3, 4, 5, 6])
            >>> processor.filter_even().get_data()
            [2, 4, 6]
        """
        try:
            self.data = [x for x in self.data if isinstance(x, (int, float)) and x % 2 == 0]
            self.processed_count += 1
            return self
        except TypeError as e:
            raise TypeError("Data must contain only numeric values") from e
    
    def multiply_by(self, factor: Union[int, float]) -> 'DataProcessor':
        """
        Multiply all values in the data by a factor.
        
        Args:
            factor (Union[int, float]): Number to multiply each value by
            
        Returns:
            DataProcessor: Self, for method chaining
            
        Example:
            >>> processor = DataProcessor([1, 2, 3])
            >>> processor.multiply_by(2).get_data()
            [2, 4, 6]
        """
        self.data = [x * factor for x in self.data]
        self.processed_count += 1
        return self
    
    def get_data(self) -> List:
        """
        Get the current processed data.
        
        Returns:
            List: A copy of the current data
            
        Note:
            Returns a copy to prevent external modification
            of the internal data structure.
        """
        return self.data.copy()
    
    def get_stats(self) -> dict:
        """
        Get basic statistics about the current data.
        
        Returns:
            dict: Statistics including count, sum, min, max, and average
            
        Example:
            >>> processor = DataProcessor([1, 2, 3, 4, 5])
            >>> stats = processor.get_stats()
            >>> stats['average']
            3.0
        """
        if not self.data:
            return {
                'count': 0,
                'sum': 0,
                'min': None,
                'max': None,
                'average': None
            }
        
        return {
            'count': len(self.data),
            'sum': sum(self.data),
            'min': min(self.data),
            'max': max(self.data),
            'average': sum(self.data) / len(self.data)
        }


def demonstrate_utilities():
    """
    Demonstrate the utility functions and classes.
    
    This function shows practical examples of how to use
    the utilities provided in this module.
    """
    print("Utility Functions Demo")
    print("=" * 30)
    
    # Email validation examples
    emails = ["user@example.com", "invalid-email", "test@domain.co.uk"]
    print("Email Validation:")
    for email in emails:
        is_valid = validate_email(email)
        status = "✓ Valid" if is_valid else "✗ Invalid"
        print(f"  {email}: {status}")
    
    # Currency formatting examples
    amounts = [19.99, 1000.50, 0, 1234567.89]
    print("\nCurrency Formatting:")
    for amount in amounts:
        formatted = format_currency(amount)
        print(f"  ${amount}{formatted}")
    
    # List chunking example
    numbers = list(range(1, 11))  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    chunks = chunk_list(numbers, 3)
    print(f"\nChunking {numbers} into groups of 3:")
    for i, chunk in enumerate(chunks):
        print(f"  Chunk {i+1}: {chunk}")
    
    # Data processor example
    print("\nData Processing:")
    processor = DataProcessor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    
    # Chain operations
    result = processor.filter_even().multiply_by(2).get_data()
    stats = processor.get_stats()
    
    print(f"  Original: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]")
    print(f"  After filter_even().multiply_by(2): {result}")
    print(f"  Stats: {stats}")


if __name__ == "__main__":
    demonstrate_utilities()

📋 Documentation Guidelines

ElementPurposeExample
Module DocstringExplain module purpose and usage"""User authentication utilities"""
Function DocstringDescribe parameters, returns, raisesArgs, Returns, Raises sections
Class DocstringExplain what class representsPurpose, key attributes, usage
Inline CommentsClarify complex logic# Calculate tax rate for region
Type HintsSpecify parameter and return typesdef func(x: int) -> str:
ExamplesShow how to use the codeDoctest format examples

🎯 Key Takeaways

🚀 What's Next?

Learn how to use type hints to make your Python code more robust and self-documenting.

Continue to: Use Type Hints

Was this helpful?

😔Poor
🙁Fair
😊Good
😄Great
🤩Excellent