📖 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
Element | Purpose | Example |
---|---|---|
Module Docstring | Explain module purpose and usage | """User authentication utilities""" |
Function Docstring | Describe parameters, returns, raises | Args, Returns, Raises sections |
Class Docstring | Explain what class represents | Purpose, key attributes, usage |
Inline Comments | Clarify complex logic | # Calculate tax rate for region |
Type Hints | Specify parameter and return types | def func(x: int) -> str: |
Examples | Show how to use the code | Doctest 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?
Track Your Learning Progress
Sign in to bookmark tutorials and keep track of your learning journey.
Your progress is saved automatically as you read.