Instructor: Brandon M. Greenwell Course: IS4010 - AI-Enhanced Application Development
๐๏ธ Welcome to Object-Oriented Programming
The foundation of scalable software architecture ๐ฏ
Object-Oriented Programming (OOP) revolutionizes how we design and structure applications by providing:
Real-world modeling: Create digital representations of real-world entities and their interactions
Career relevance: Essential for modern software development, system design interviews, and team collaboration
Scalability: Build applications that can grow from prototypes to enterprise-level systems
Code organization: Transform chaotic procedural code into elegant, maintainable architectures
๐ Additional Notes: Historical context: OOP emerged in the 1960s with Simula, popularized by Smalltalk in the 1970s. Industry adoption: Nearly every major software system uses OOP principles - from operating systems to web frameworks. Career impact: OOP concepts appear in 90% of technical interviews for software engineering roles.
By the end of this notebook, you will be able to: - Distinguish when to use classes instead of functions for complex state management - Create classes with constructors (__init__) and string representations (__str__) - Implement methods that give objects behavior and encapsulate functionality - Use inheritance to create specialized classes and enable code reuse - Apply advanced OOP concepts like properties, class methods, and static methods
How to Use This Notebook
Run each cell by clicking the play button or pressing Shift+Enter
Experiment with the code - modify values and see what happens
Complete the exercises marked with ๐๏ธโโ๏ธ for hands-on practice
Use AI assistants to help you understand concepts or explore variations
Session 1: From Functions to Objects
๐ค When Functions Arenโt Enough: A Shopping Cart Story
The Problem: Managing complex state with functions
Building an e-commerce shopping cart system reveals the limitations of the function-only approach:
Scenario: Creating an online shopping cart (like Amazon, Target, or any e-commerce site)
Challenge: Multiple pieces of related data (items, totals, discounts, tax rates) need coordination
Function approach: Becomes unwieldy as complexity grows
Real pain points: Parameter passing chaos, state management nightmares, code duplication
Real-World Shopping Cart Requirements
Add/remove items with quantities
Calculate subtotals and tax
Apply discounts and promotions
Save cart state between sessions
Handle multiple payment methods
Track inventory availability
โ The Function Approach: Shopping Cart Chaos
Watch how quickly this becomes unwieldy as we try to manage cart state with only functions:
# Shopping cart implementation using functions - gets messy fast!def create_cart():"""Create a new empty shopping cart."""return {"items": [], "subtotal": 0, "tax_rate": 0.08, "discount": 0}def add_item(cart, name, price, quantity=1):"""Add an item to the cart. Parameters ---------- cart : dict The shopping cart to which the item will be added. name : str The name of the item. price : float The price of a single unit of the item. quantity : int, optional The quantity of the item to add (default is 1). Returns ------- dict The updated shopping cart with the new item added and subtotal updated. """ cart["items"].append({"name": name, "price": price, "qty": quantity}) cart["subtotal"] += price * quantityreturn cartdef apply_discount(cart, discount_percent):"""Apply a discount to the cart.""" cart["discount"] = discount_percentreturn cartdef calculate_total(cart):"""Calculate the final total after discount and tax.""" discounted = cart["subtotal"] * (1- cart["discount"]/100)return discounted * (1+ cart["tax_rate"])def display_cart(cart):"""Display cart contents."""print("Shopping Cart:")for item in cart["items"]:print(f" {item['name']} x{item['qty']} - ${item['price']:.2f} each")print(f" Subtotal: ${cart['subtotal']:.2f}")if cart["discount"] >0:print(f" Discount: {cart['discount']}%")print(f" Total: ${calculate_total(cart):.2f}")# Usage - notice how cumbersome this becomescart = create_cart()cart = add_item(cart, "Laptop", 999.99)cart = add_item(cart, "Mouse", 29.99)cart = apply_discount(cart, 10)display_cart(cart)some_unrelated_dict = {"items": ["a", "b"]}add_item(some_unrelated_dict, "Test", 10) # This will cause an error since the dict doesn't have the expected structure!
Shopping Cart:
Laptop x1 - $999.99 each
Mouse x1 - $29.99 each
Subtotal: $1029.98
Discount: 10%
Total: $1001.14
---------------------------------------------------------------------------KeyError Traceback (most recent call last)
CellIn[6], line 57 54 display_cart(cart)
56 some_unrelated_dict = {"items": ["a", "b"]}
---> 57add_item(some_unrelated_dict,"Test",10)# This will cause an error since the dict doesn't have the expected structure!CellIn[6], line 26, in add_item(cart, name, price, quantity) 8"""Add an item to the cart. 9 10Parameters (...) 23 24""" 25 cart["items"].append({"name": name, "price": price, "qty": quantity})
---> 26cart["subtotal"] += price * quantity
27return cart
KeyError: 'subtotal'
๐๏ธโโ๏ธ Exercise 1: Feel the Pain
Try to add more functionality to the function-based cart: 1. Add a function to remove items 2. Add a function to change item quantities 3. Notice how many parameters you need to pass around!
# Your code here - try adding remove_item() and change_quantity() functions# Notice how complex the parameter passing becomes!def remove_item(cart, name):"""Remove an item from the cart."""for item in cart["items"]:if item["name"] == name: cart["subtotal"] -= item["price"] * item["qty"] cart["items"].remove(item)breakreturn cartdef change_quantity(cart, name, quantity):"""Change the quantity of an item in the cart."""for item in cart["items"]:if item["name"] == name: cart["subtotal"] -= item["price"] * item["qty"] item["qty"] = quantity cart["subtotal"] += item["price"] * item["qty"]breakreturn cart
โ The OOP Solution: Clean and Scalable
Now letโs see the dramatic improvement with object-oriented programming:
Why Classes Excel Here
Encapsulation: All cart data and behavior bundled together
State management: No parameter passing chaos - data lives with the object
Extensibility: Easy to add new methods and features
Maintainability: Changes are localized to the class
๐ Additional Notes: Professional context: Every major framework (Django, Flask, React) uses this pattern extensively. Code organization: Classes provide natural boundaries for related functionality. Testing: Much easier to unit test class methods than function chains.
class ShoppingCart:"""Represents a shopping cart with items, discounts, and tax calculation."""def__init__(self, tax_rate=0.08):"""Initialize a new shopping cart."""self.items = []self.tax_rate = tax_rateself.discount_percent =0def add_item(self, name, price, quantity=1):"""Add an item to the cart."""self.items.append({"name": name, "price": price, "qty": quantity})def apply_discount(self, discount_percent):"""Apply a discount to the entire cart."""self.discount_percent = discount_percent@propertydef subtotal(self):"""Calculate subtotal before discount and tax."""returnsum(item["price"] * item["qty"] for item inself.items)@propertydef total(self):"""Calculate final total after discount and tax.""" discounted =self.subtotal * (1-self.discount_percent/100)return discounted * (1+self.tax_rate)def__str__(self):"""Return a string representation of the cart.""" lines = ["Shopping Cart:"]for item inself.items: lines.append(f" {item['name']} x{item['qty']} - ${item['price']:.2f} each") lines.append(f" Subtotal: ${self.subtotal:.2f}")ifself.discount_percent >0: lines.append(f" Discount: {self.discount_percent}%") lines.append(f" Total: ${self.total:.2f}")return"\n".join(lines)# Usage is much cleaner!cart = ShoppingCart()#print(cart)print(cart.items)cart.add_item("Laptop", 999.99)#print(cart)cart.add_item("Mouse", 29.99)cart.apply_discount(10)#print(cart)print(cart.items)print(cart)
Add methods to the ShoppingCart class: 1. remove_item(name) - remove an item by name 2. clear() - empty the cart 3. item_count - property that returns total number of items
class ShoppingCart:"""Represents a shopping cart with items, discounts, and tax calculation."""def__init__(self, tax_rate=0.08):"""Initialize a new shopping cart."""self.items = []self.tax_rate = tax_rateself.discount_percent =0def add_item(self, name, price, quantity=1):"""Add an item to the cart."""self.items.append({"name": name, "price": price, "qty": quantity})def remove_item(self, name):"""Remove an item from the cart by name."""# Keep only items that don't match the nameself.items = [item for item inself.items if item["name"] != name]def clear(self):"""Empty the cart."""self.items = []def apply_discount(self, discount_percent):"""Apply a discount to the entire cart."""self.discount_percent = discount_percent@propertydef item_count(self):"""Return total number of items in the cart."""returnsum(item["qty"] for item inself.items)@propertydef subtotal(self):"""Calculate subtotal before discount and tax."""returnsum(item["price"] * item["qty"] for item inself.items)@propertydef total(self):"""Calculate final total after discount and tax.""" discounted =self.subtotal * (1-self.discount_percent/100)return discounted * (1+self.tax_rate)def__str__(self):"""Return a string representation of the cart.""" lines = ["Shopping Cart:"]ifnotself.items: lines.append(" (Empty)")else:for item inself.items: lines.append(f" {item['name']} x{item['qty']} - ${item['price']:.2f} each") lines.append(f" Subtotal: ${self.subtotal:.2f}")ifself.discount_percent >0: lines.append(f" Discount: {self.discount_percent}%") lines.append(f" Total: ${self.total:.2f}") lines.append(f" Item Count: {self.item_count}")return"\n".join(lines)# Test the enhanced classcart = ShoppingCart()cart.add_item("Laptop", 999.99)cart.add_item("Mouse", 29.99, 2)print("--- Initial Cart ---")print(cart)cart.remove_item("Mouse")print("\n--- After removing Mouse ---")print(cart)cart.clear()print("\n--- After clearing ---")print(cart)
Part 2: Understanding Classes and Objects
๐ง The __init__ Method: Object Construction
Understanding the constructor method is fundamental to creating useful classes:
Automatic execution: Runs immediately when a new object is created
Purpose: Initialize the objectโs attributes with starting values
The self parameter: References the specific instance being created
Convention: Always the first parameter in instance methods
๐ Additional Notes: Modern Python: Use type hints for better code clarity and IDE support. Instance attributes: Each object has its own copy of the data in separate memory. Common errors: Forgetting self or incorrect parameter order are frequent mistakes when starting with OOP.
Key Constructor Concepts
Instance attributes: Each object has its own copy of the data
Parameter validation: Constructors can enforce business rules
Default values: Provide sensible defaults for optional parameters
Documentation: Docstrings explain the class purpose and usage
Basic Class Structure
Letโs explore the fundamental concepts with a simple example:
class BankAccount:"""Represents a bank account with balance tracking."""def__init__(self, account_holder: str, initial_balance: float=0.0):"""Initialize a new bank account."""# Instance attributes - unique to each accountself.account_holder = account_holderself.balance = initial_balanceself.transaction_history = []def deposit(self, amount: float):"""Add money to the account."""self.balance += amountself.transaction_history.append(f"Deposited ${amount:.2f}")print(f"Deposited ${amount:.2f}. New balance: ${self.balance:.2f}")def withdraw(self, amount: float):"""Remove money from the account if sufficient funds exist."""if amount <=self.balance:self.balance -= amountself.transaction_history.append(f"Withdrew ${amount:.2f}")print(f"Withdrew ${amount:.2f}. New balance: ${self.balance:.2f}")else:print(f"Insufficient funds! Current balance: ${self.balance:.2f}")def__str__(self):"""Return a string representation of the account."""returnf"Account holder: {self.account_holder}, Balance: ${self.balance:.2f}"# Create specific account instancesalice_account = BankAccount("Alice Johnson", 1000.0)bob_account = BankAccount("Bob Smith") # Uses default balance of 0.0print(alice_account)print(bob_account)# Each account operates independentlyalice_account.deposit(500)bob_account.deposit(100)alice_account.withdraw(200)print("\nFinal states:")print(alice_account)print(bob_account)
๐๏ธโโ๏ธ Exercise 3: Bank Account Features
Enhance the BankAccount class: 1. Add a get_transaction_history() method 2. Add an account_number attribute (you can use a simple counter) 3. Add a minimum balance requirement
# Your enhanced BankAccount class here
๐ The __str__ Method: Human-Readable Representation
Making your objects print meaningfully is essential for debugging and user interfaces:
The __str__ method defines how objects appear when printed
Automatic invocation: Called by print(), str(), and string formatting
User-friendly: Should return meaningful information for end users
AI assistant friendly: Perfect boilerplate code to generate with AI tools
Debugging essential: Makes testing and troubleshooting much easier
๐ Additional Notes: Professional practice: All production classes should have meaningful __str__ methods - they save hours of debugging time. String formatting: Modern Python uses f-strings for clean, readable string construction. Unicode symbols: Adding symbols like โ makes output more engaging and readable.
String Representation Best Practices
Include key identifying information: Name, ID, or other unique attributes
Keep it concise but informative: One line is usually sufficient
Use consistent formatting: Establish patterns across your application
Consider the audience: End users vs developers may need different information
from datetime import datetimeclass User:"""Represents a user in a social media application."""def__init__(self, username: str, email: str, join_date: str=None):self.username = usernameself.email = emailself.join_date = join_date or datetime.now().strftime("%Y-%m-%d")self.followers =0self.following =0self.posts = []self.is_verified =Falsedef follow_user(self, other_user):"""Follow another user."""self.following +=1 other_user.followers +=1print(f"@{self.username} is now following @{other_user.username}")def create_post(self, content: str):"""Create a new post.""" post = {"content": content,"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M"),"likes": 0 }self.posts.append(post)print(f"@{self.username} posted: {content}")def verify_account(self):"""Verify the user account."""self.is_verified =Trueprint(f"@{self.username} is now verified! โ")def__str__(self) ->str:"""Return a user-friendly string representation.""" verification ="โ"ifself.is_verified else""returnf"@{self.username}{verification} ({self.followers} followers, {self.following} following) - Joined {self.join_date}"# Create some users and let them interactgrace = User("grace_hopper", "grace@example.com", "2023-01-15")ada = User("ada_lovelace", "ada@example.com", "2023-02-20")print(grace)print(ada)# Simulate social media activitygrace.create_post("Debugging is like being the detective in a crime movie where you are also the murderer.")ada.follow_user(grace)ada.verify_account()print("\nUpdated profiles:")print(grace)print(ada)
๐๏ธโโ๏ธ Exercise 4: Social Media Features
Extend the User class with: 1. A like_post(user, post_index) method 2. A get_recent_posts(count=5) method 3. A bio attribute and update_bio(new_bio) method
# Your enhanced User class here
Part 3: Methods and Behavior
๐ฎ Understanding Object Behavior Through Game Characters
Methods define what objects can DO, not just what data they store:
Why Methods Matter
Behavior encapsulation: Actions are logically grouped with related data
State management: Methods can modify object attributes safely
Code organization: Related functionality stays together
Interface design: Methods define how other code interacts with your objects
๐ Additional Notes: Private methods: Use the _method_name convention for internal helper methods. Method design: Good method interfaces are easy to use correctly and hard to use incorrectly.
Method Types Youโll Encounter
Action methods: Perform operations that change object state (attack, deposit, add_item)
Query methods: Return information without changing state (get_balance, is_alive)
Factory methods: Create new instances or related objects
Utility methods: Helper functions that support the main functionality
Game Character Example
Letโs create a more complex example with a game character that demonstrates how methods work together:
import randomclass GameCharacter:"""Represents a character in a role-playing game."""def__init__(self, name: str, health: int=100):self.name = nameself.health = healthself.max_health = healthself.experience =0self.level =1self.inventory = []def take_damage(self, damage: int):"""Reduce character health, ensuring it doesn't go below 0."""self.health =max(0, self.health - damage)print(f"๐ฅ {self.name} takes {damage} damage! Health: {self.health}/{self.max_health}")ifself.health ==0:print(f"๐ {self.name} has been defeated!")def heal(self, amount: int):"""Restore character health, capped at maximum.""" old_health =self.healthself.health =min(self.max_health, self.health + amount) healed =self.health - old_healthprint(f"โค๏ธ {self.name} heals for {healed} points! Health: {self.health}/{self.max_health}")def gain_experience(self, exp: int):"""Add experience and level up if threshold is reached."""self.experience += expprint(f"โจ {self.name} gains {exp} experience! Total: {self.experience}")# Simple leveling formula: need level * 100 exp to level upifself.experience >=self.level *100:self._level_up()def _level_up(self):"""Private method to handle leveling up."""self.level +=1 health_increase =20self.max_health += health_increaseself.health =self.max_health # Full heal on level upprint(f"๐ {self.name} reached level {self.level}! Max health increased by {health_increase}!")def attack(self, target):"""Attack another character.""" damage = random.randint(15, 25) + (self.level -1) *5print(f"โ๏ธ {self.name} attacks {target.name}!") target.take_damage(damage)# Gain experience for fightingif target.health ==0:self.gain_experience(50)else:self.gain_experience(10)def add_to_inventory(self, item: str):"""Add an item to the character's inventory."""self.inventory.append(item)print(f"๐ฆ {self.name} found: {item}")def__str__(self):returnf"{self.name} (Level {self.level}) - Health: {self.health}/{self.max_health}, XP: {self.experience}"# Create characters and simulate gameplayhero = GameCharacter("Aria the Brave")monster = GameCharacter("Goblin Warrior", 80)print("=== Game Start ===")print(hero)print(monster)print()# Simulate a battleprint("=== Battle Begins ===")hero.attack(monster)monster.attack(hero)hero.attack(monster)hero.heal(15)print("\n=== Final Status ===")print(hero)print(monster)
๐๏ธโโ๏ธ Exercise 5: Game Character Enhancement
Add these features to the GameCharacter class: 1. A use_potion() method that heals based on inventory 2. A magic_attack(target) method with different damage 3. A get_inventory_value() method that calculates total value
# Your enhanced GameCharacter class here
Part 4: Inheritance - Code Reuse and Specialization
๐ Vehicle Hierarchy: Understanding Inheritance
Inheritance allows you to create specialized versions of existing classes:
Core Inheritance Concepts
Base class (parent): Contains common attributes and methods shared by all subclasses
Derived class (child): Inherits from base class and adds specialized behavior
Method overriding: Child classes can replace parent methods with specialized versions
super() function: Calls parent class methods from child class
Code reuse: Write common functionality once, use it everywhere
๐ Additional Notes: Design principle: Inheritance should represent โis-aโ relationships (ElectricCar IS-A Vehicle), not just code sharing. Inheritance vs composition: Sometimes composition (has-a relationships) is better than inheritance. Method resolution order: Python searches parent classes in a specific order when looking for methods.
Shared behavior: Multiple classes need similar functionality
Specialization: Different types need slightly different behavior
Framework design: Plugin architectures often use inheritance
Professional Inheritance Examples
Web frameworks: BaseView, ListView, DetailView in Django
Game engines: GameObject, Player, Enemy, Projectile
GUI frameworks: Widget, Button, TextBox, Menu
Vehicle Hierarchy Example
Letโs explore inheritance with a vehicle example that shows code reuse and specialization:
class Vehicle:"""Base class for all vehicles."""def__init__(self, make: str, model: str, year: int):self.make = makeself.model = modelself.year = yearself.mileage =0self.is_running =Falsedef start_engine(self):"""Start the vehicle's engine."""ifnotself.is_running:self.is_running =Trueprint(f"๐ The {self.year}{self.make}{self.model} engine starts.")else:print(f"The {self.make}{self.model} is already running.")def stop_engine(self):"""Stop the vehicle's engine."""ifself.is_running:self.is_running =Falseprint(f"๐ The {self.make}{self.model} engine stops.")else:print(f"The {self.make}{self.model} is already stopped.")def drive(self, miles: float):"""Drive the vehicle and update mileage."""ifself.is_running:self.mileage += milesprint(f"๐ฃ๏ธ Drove {miles} miles. Total mileage: {self.mileage}")else:print("Cannot drive - engine is not running!")def__str__(self): status ="running"ifself.is_running else"stopped"returnf"{self.year}{self.make}{self.model} - {self.mileage} miles ({status})"# Test the base Vehicle classgeneric_car = Vehicle("Honda", "Civic", 2020)print(generic_car)generic_car.start_engine()generic_car.drive(25)print(generic_car)
๐ Electric vs Gas Cars: Method Overriding in Action
Watch how inheritance enables specialized behavior while maintaining a common interface:
Method Overriding Benefits
Polymorphism: Same method name, different behavior based on object type
Interface consistency: All vehicles can start_engine() and drive()
Specialized behavior: Electric cars handle battery, gas cars handle fuel
Extensibility: Easy to add new vehicle types later
๐ Additional Notes: Real-world example: Tesla vs Toyota - same basic concept (transportation), completely different implementations. Method signatures: Overridden methods should have compatible signatures with the parent method. Polymorphism: This pattern enables polymorphism, a key concept in advanced OOP.
class ElectricCar(Vehicle):"""Electric vehicle with battery management."""def__init__(self, make: str, model: str, year: int, battery_capacity: float):# Call the parent class constructorsuper().__init__(make, model, year)# Add electric-specific attributesself.battery_capacity = battery_capacityself.battery_level =100.0# Start fully chargedself.charging =Falsedef start_engine(self):"""Override: Electric cars don't have traditional engines."""ifnotself.is_running:ifself.battery_level >0:self.is_running =Trueprint(f"๐ The {self.year}{self.make}{self.model} powers on silently.")else:print(f"โ Cannot start - battery is empty!")else:print(f"The {self.make}{self.model} is already running.")def drive(self, miles: float):"""Override: Driving uses battery power."""ifself.is_running andself.battery_level >0:# Electric cars use about 0.3% battery per mile battery_used = miles *0.3self.battery_level =max(0, self.battery_level - battery_used)self.mileage += milesprint(f"๐ฃ๏ธ Drove {miles} miles silently. Battery: {self.battery_level:.1f}%")ifself.battery_level ==0:self.is_running =Falseprint(f"๐ Battery empty! The {self.make}{self.model} has stopped.")elifnotself.is_running:print("Cannot drive - car is not powered on!")else:print("Cannot drive - battery is empty!")def charge(self, hours: float):"""Charge the battery (unique to electric cars)."""ifself.charging:print("Already charging!")returnself.charging =True# Assume 10% charge per hour charge_added =min(hours *10, 100-self.battery_level)self.battery_level += charge_addedprint(f"๐ Charged for {hours} hours. Battery: {self.battery_level:.1f}%")self.charging =Falsedef__str__(self):"""Override to include battery information.""" base_info =super().__str__()returnf"{base_info} - Battery: {self.battery_level:.1f}%"# Test the electric cartesla = ElectricCar("Tesla", "Model S", 2023, 100.0)print(tesla)tesla.start_engine() # Uses overridden methodtesla.drive(50) # Uses overridden methodtesla.drive(200) # Should drain batterytesla.charge(2) # Uses unique methodtesla.start_engine() # Should work againprint("\nFinal state:")print(tesla)
Traditional Car: Another Child Class
class GasCar(Vehicle):"""Traditional gas-powered vehicle."""def__init__(self, make: str, model: str, year: int, tank_size: float):super().__init__(make, model, year)self.tank_size = tank_sizeself.fuel_level = tank_size # Start with full tankdef drive(self, miles: float):"""Override: Driving uses fuel."""ifself.is_running andself.fuel_level >0:# Assume 25 miles per gallon fuel_used = miles /25self.fuel_level =max(0, self.fuel_level - fuel_used)self.mileage += milesprint(f"๐ฃ๏ธ Drove {miles} miles. Fuel remaining: {self.fuel_level:.1f} gallons")ifself.fuel_level ==0:self.is_running =Falseprint(f"โฝ Out of gas! The {self.make}{self.model} has stopped.")elifnotself.is_running:print("Cannot drive - engine is not running!")else:print("Cannot drive - out of gas!")def fill_tank(self):"""Fill the gas tank.""" gallons_added =self.tank_size -self.fuel_levelself.fuel_level =self.tank_sizeprint(f"โฝ Added {gallons_added:.1f} gallons. Tank is full!")def__str__(self): base_info =super().__str__()returnf"{base_info} - Fuel: {self.fuel_level:.1f}/{self.tank_size} gallons"# Test the gas cartruck = GasCar("Ford", "F-150", 2022, 20.0)print(truck)truck.start_engine()truck.drive(100)truck.drive(400) # Should use a lot of fueltruck.fill_tank()print("\nComparison:")print(tesla)print(truck)
๐๏ธโโ๏ธ Exercise 6: Vehicle Inheritance
Create a new vehicle type: 1. Create a Motorcycle class that inherits from Vehicle 2. Override the start_engine() method with a motorcycle-specific message 3. Add a wheelie() method unique to motorcycles 4. Make motorcycles more fuel-efficient in the drive() method
# Your Motorcycle class here
Part 5: Advanced OOP Concepts
โ๏ธ Properties, Class Methods, and Static Methods
Moving beyond basic classes to professional Python development:
Advanced Features Overview
Properties: Control attribute access with getter/setter methods
Class methods: Methods that operate on the class rather than instances
Static methods: Utility functions that belong with the class logically
Class variables: Data shared across all instances of a class
๐ Additional Notes: Professional context: These features are heavily used in frameworks like Django and Flask. Property pattern: Common for validation, computed values, and maintaining API compatibility. Class methods: Often used for alternative constructors (factory pattern) and class-level operations.
When to Use Advanced Features
Properties: When you need to validate data or compute values dynamically
Class methods: For alternative constructors, factory methods, or class-level operations
Static methods: For utility functions related to the class
Class variables: For constants or data shared across all instances
Advanced Product Class Example
This comprehensive example demonstrates professional Python OOP techniques used in real applications:
Key Advanced Concepts Demonstrated
Property decorators: @property and @property.setter for controlled attribute access
๐ Additional Notes: E-commerce relevance: This pattern is used in systems like Amazon, Shopify, and WooCommerce. Property advantages: Properties allow you to change internal implementation without breaking code that uses your class. Validation patterns: Essential for data integrity in production systems.
class Product:"""Represents a product in an inventory system."""# Class variable - shared across all instances total_products_created =0 tax_rate =0.08# Default tax ratedef__init__(self, name: str, price: float, category: str="General"):self.name = nameself._price = price # Private attribute (convention)self.category = categoryself.quantity_sold =0# Increment class variable Product.total_products_created +=1@propertydef price(self) ->float:"""Get the product price."""returnself._price@price.setterdef price(self, value: float):"""Set the product price with validation."""if value <0:raiseValueError("Price cannot be negative")self._price = valueprint(f"Price updated to ${value:.2f}")@propertydef price_with_tax(self) ->float:"""Calculate price including tax."""returnself._price * (1+ Product.tax_rate)@classmethoddef get_total_products(cls) ->int:"""Return total number of products created."""return cls.total_products_created@classmethoddef set_tax_rate(cls, new_rate: float):"""Update the tax rate for all products.""" cls.tax_rate = new_rateprint(f"Tax rate updated to {new_rate:.2%}")@staticmethoddef calculate_discount(price: float, discount_percent: float) ->float:"""Calculate discounted price (utility function)."""return price * (1- discount_percent /100)def sell(self, quantity: int=1):"""Record a sale of this product."""self.quantity_sold += quantity revenue =self.price * quantityprint(f"Sold {quantity}{self.name}(s) for ${revenue:.2f}")def__str__(self):returnf"{self.name} - ${self.price:.2f} ({self.category})"# Demonstrate advanced featuresprint(f"Products created so far: {Product.get_total_products()}")laptop = Product("Gaming Laptop", 1299.99, "Electronics")mouse = Product("Wireless Mouse", 79.99, "Electronics")book = Product("Python Programming", 49.99, "Books")print(f"\nProducts created so far: {Product.get_total_products()}")# Property usageprint(f"\nLaptop price: ${laptop.price:.2f}")print(f"Laptop price with tax: ${laptop.price_with_tax:.2f}")# Price validationtry: laptop.price =1199.99# Valid# laptop.price = -100 # Would raise ValueErrorexceptValueErroras e:print(f"Error: {e}")# Class method usageProduct.set_tax_rate(0.10) # Update tax rate for all productsprint(f"Laptop price with new tax: ${laptop.price_with_tax:.2f}")# Static method usagediscounted_price = Product.calculate_discount(laptop.price, 15)print(f"Laptop with 15% discount: ${discounted_price:.2f}")# Record saleslaptop.sell(2)mouse.sell(5)print(f"\nFinal inventory:")for product in [laptop, mouse, book]:print(f"{product} - Sold: {product.quantity_sold}")
๐๏ธโโ๏ธ Exercise 7: Advanced Features
Enhance the Product class: 1. Add a stock_quantity attribute and property with validation 2. Create a @classmethod called create_electronics(name, price) that sets category automatically 3. Add a @staticmethod for calculating bulk discount rates
# Your enhanced Product class here
Part 6: Lab 06 Practice - Putting It All Together
๐ Applying OOP Concepts to Books
Now letโs apply everything youโve learned to the actual Lab 06 requirements:
Lab 06 Concepts Review
Class creation: Book class with proper constructor and string representation
Inheritance: EBook class that extends Book with additional functionality
Method implementation: get_age() method with business logic
Method overriding: Enhanced __str__ method in the child class
๐ Additional Notes: Real-world relevance: Digital libraries like Kindle, Apple Books, and Google Books use similar class hierarchies. Design decisions: The file_size attribute belongs to EBook, not Book, because physical books donโt have file sizes. Future extensions: This design easily allows for additional book types like AudioBook or PhysicalBook.
Key Implementation Points
Constructor parameters: Required vs optional parameters with defaults
Data validation: Consider what validation makes sense for book data
Method logic: The get_age() calculation using current year
Inheritance chain: How EBook builds upon Book functionality
Book Class Implementation
Letโs work on the exact requirements for Lab 06:
class Book:"""Represents a book with title, author, and publication year."""def__init__(self, title: str, author: str, year: int):"""Initialize a new book."""self.title = titleself.author = authorself.year = yeardef get_age(self) ->int:"""Calculate and return the age of the book based on its publication year.""" current_year =2025# As specified in the labreturn current_year -self.yeardef__str__(self) ->str:"""Return a user-friendly string representation of the book."""returnf"\"{self.title}\" by {self.author} ({self.year})"# Test the Book classbook1 = Book("The Pragmatic Programmer", "Andy Hunt", 1999)print(book1)print(f"Age: {book1.get_age()} years")book2 = Book("Clean Code", "Robert Martin", 2008)print(book2)print(f"Age: {book2.get_age()} years")
EBook Class with Inheritance
class EBook(Book):"""Electronic book that inherits from Book."""def__init__(self, title: str, author: str, year: int, file_size: int):"""Initialize an EBook with all Book attributes plus file size."""# Call the parent class constructorsuper().__init__(title, author, year)# Add the EBook-specific attributeself.file_size = file_sizedef__str__(self) ->str:"""Override to include file size information."""# Get the base string from the parent class base_str =super().__str__()# Append the file size informationreturnf"{base_str} ({self.file_size} MB)"# Test the EBook classebook1 = EBook("Automate the Boring Stuff with Python", "Al Sweigart", 2015, 12)print(ebook1)print(f"Age: {ebook1.get_age()} years") # Inherited methodebook2 = EBook("Python Crash Course", "Eric Matthes", 2019, 8)print(ebook2)print(f"Age: {ebook2.get_age()} years")# Demonstrate that both classes work togetherlibrary = [book1, book2, ebook1, ebook2]print("\n=== My Library ===")for item in library:print(f"{item} - {item.get_age()} years old")
๐๏ธโโ๏ธ Exercise 8: Complete Lab 06
Create your own implementation of the Book and EBook classes following the exact lab requirements: 1. Implement the Book class with the specified attributes and methods 2. Implement the EBook class that inherits from Book 3. Test both classes thoroughly 4. Try adding additional features like a AudioBook class
# Your complete Lab 06 implementation here# This is your chance to practice everything you've learned!if__name__=='__main__':# Test your classes herepass
Summary and Next Steps
What Youโve Learned ๐ฏ
When to use classes vs functions - Complex state management, related data and behavior encapsulation
Class fundamentals - __init__, __str__, attributes, and methods
Real-world modeling - Bank accounts, social media users, game characters, vehicles
ChatGPT/Claude prompts: โHelp me design a class hierarchy for [your domain]โ
GitHub Copilot: Generate boilerplate code, constructor methods, and docstrings
Code review: Ask AI assistants to review your class designs for best practices
Final Thoughts ๐ญ
Object-oriented programming is not just about syntax - itโs about thinking in terms of objects, their responsibilities, and their interactions. This mindset will serve you well throughout your programming career, from designing simple scripts to architecting enterprise applications.
The patterns youโve learned here - encapsulation, inheritance, and method design - form the foundation of modern software development. As you continue your journey, youโll see these concepts everywhere: in web frameworks, mobile apps, data science libraries, and game engines.
Practice, experiment, and most importantly, think like an object-oriented programmer! ๐