← Back to Learning Hub

Lesson 5: Code Readability & Class Mastery

Answering your question and leveling up your code

90 minutes Refactoring Classes

Today's Goal

You asked: "Do you have any tips for improving the readability of code?"

Today we'll answer that question directly, review your homework code, fix some bugs, and refactor your shop system into clean, readable code.

Your observation: "The store page has gotten a bit too cluttered for me to read it nicely."

This is a great observation! Recognizing when code needs cleanup is an important skill.

Part 1: Code Readability Principles 10 min

The 5 Pillars of Readable Code

1. Meaningful Names

Variables and functions should describe what they hold or do.

Instead of... Use...
cont is_shopping or continue_shopping
flag is_valid_input or budget_entered
cart (for item data) item_data or selected_item

2. Whitespace & Grouping

Use blank lines to separate logical sections. Group related code together.

3. Comments (When Needed)

Explain why, not what. The code shows what; comments explain reasoning.

4. Short Functions

Each function should do one thing. If a function is getting long, split it up.

5. Consistent Style

Pick a style and stick to it. Same naming conventions, same spacing, same patterns.

Quick Check

Looking at your shop code, which of these 5 pillars could use the most improvement?

Part 2: Your Homework Review 10 min

What You Did Well

  • Created a full class - Your history class has proper structure
  • Added custom methods - back(), goback(), view() show initiative
  • Error handling - IndexError in every method that could fail
  • Input normalization - .strip().lower() used consistently
  • Created an Undo class - Shows you're applying Lesson 4 concepts
Your history class - Well structured!
class history:
    def __init__(self):
        self.items = []

    def enqueue(self, item):
        self.items.append(item)

    def back(self):
        if self.is_empty():
            raise IndexError("Cannot see an empty queue")
        return self.items[-1]

    def goback(self):
        if self.is_empty():
            raise IndexError("Cannot see an empty queue")
        return self.items[-2]

    # ... more methods

Nice touch: Your goback() method returns the second-to-last item - this shows you're thinking about how browser navigation actually works!

Part 3: Class vs Instance - The Critical Bug 15 min

Let's Look at This Line

In your shop code, you wrote:

Your Code
history = Undo
history.items(purchase)

Bug Found

history = Undo assigns the class itself to the variable, not an instance of it.

Think of it this way: Undo is a blueprint, Undo() builds something from that blueprint.

The Difference

Code What It Does Analogy
history = Undo Assigns the class (blueprint) to history Holding the blueprint for a house
history = Undo() Creates a new instance (object) and assigns it Actually building a house from the blueprint
Visual Example
class Dog:
    def __init__(self):
        self.name = "Unknown"

    def bark(self):
        print(f"{self.name} says woof!")

# This is the CLASS (blueprint)
print(Dog)           # Output: <class '__main__.Dog'>

# This is an INSTANCE (actual dog object)
my_dog = Dog()
print(my_dog)        # Output: <__main__.Dog object at 0x...>

# Only instances have the attributes set by __init__
my_dog.name = "Max"
my_dog.bark()        # Output: Max says woof!

# The class itself doesn't have .name set
# Dog.bark()         # ERROR! No 'self' to refer to

Discussion

  • When you write Undo(), what method runs automatically?
  • What does that method set up for the new instance?

Answer

Click to Reveal

__init__ runs automatically. It sets up self.items = [], giving the new instance its own empty list to work with.

The Second Bug on That Line

Even after fixing Undo to Undo(), there's another issue:

Bug
history.items(purchase)  # items is a LIST, not a method!
Fix
history.add(purchase)    # add() is the method you defined

Part 4: The Scope Bug 10 min

Where You Create the Instance Matters

Look at where history = Undo appears in your code:

Your Code - Spot the Problem
while cont == True:
    # ... shopping loop ...

    purchase = input("Please enter the item: ").strip().lower()
    shopping_list.append(purchase)
    price += cart[0]

    history = Undo       # <-- Created INSIDE the loop!
    history.items(purchase)

    # ... rest of loop ...

The Problem

Even if you fix Undo to Undo(), creating it inside the loop means:

Every iteration creates a brand new instance with an empty items list. All previous items are lost!

The Fix - Create BEFORE the Loop
undo_history = Undo()    # Create ONCE, before the loop

while continue_shopping:
    # ... shopping code ...

    purchase = input("Please enter the item: ").strip().lower()
    shopping_list.append(purchase)
    price += item_data[0]

    undo_history.add(purchase)  # Use the existing instance

    # ... rest of loop ...

Rule: If you want an object to persist across loop iterations, create it before the loop starts.

Part 5: Quick Bug Fix 5 min

is vs in

In your browser history code:

Bug
if action_return is ["y", "yes", "1"]:  # WRONG!
Fix
if action_return in ["y", "yes", "1"]:  # Correct!

The Difference

Operator What It Checks Example
is Same object in memory (identity) a is b - Are a and b the exact same object?
in Membership in a collection "y" in ["y", "yes"] - Is "y" in this list?

Remember

A string will never be the same object as a list, so is with a list will always be False.

Part 6: Refactoring Your Shop Code 25 min

The Plan

Let's reorganize your shop code to be more readable. We'll use classes and functions to separate concerns.

Current Problems:

  • Everything in one long script
  • Hard to find where things happen
  • Variables scattered throughout
  • Repeated code patterns

Our Goal:

  • Group related code into classes
  • Clear sections with comments
  • Each piece does one job
  • Easy to read from top to bottom

Step 1: Identify the Components

What "things" exist in your shop?

  • Shop - Has items, displays them
  • Cart - Tracks purchases, can undo
  • Customer - Has budget, membership status
Refactored: Cart Class
class Cart:
    """Manages shopping cart with undo functionality."""

    def __init__(self):
        self.items = []        # List of purchased items
        self.history = []      # For undo feature

    def add_item(self, item_name, price):
        """Add an item to the cart."""
        self.items.append(item_name)
        self.history.append({'item': item_name, 'price': price})
        print(f"Added {item_name} to cart")

    def undo_last(self):
        """Remove the last added item. Returns the price refunded."""
        if not self.history:
            print("Nothing to undo!")
            return 0

        last_action = self.history.pop()
        self.items.remove(last_action['item'])
        print(f"Removed {last_action['item']} from cart")
        return last_action['price']

    def get_total(self):
        """Calculate total from history."""
        return sum(action['price'] for action in self.history)

    def show(self):
        """Display current cart contents."""
        if not self.items:
            print("Cart is empty")
        else:
            print(f"Cart: {', '.join(self.items)}")
            print(f"Total: ${self.get_total():.2f}")
Refactored: Shop Class
class Shop:
    """Manages store inventory and display."""

    def __init__(self):
        self.items = {
            'apple': {'price': 1, 'description': "An apple a day keeps the doctor away"},
            'toothpaste': {'price': 3, 'description': "Keep the dentist away"},
            'toothbrush': {'price': 200, 'description': "What are you gonna brush with?"},
            'watch': {'price': 50, 'description': "Time to get a watch"}
        }

    def display_items(self):
        """Show all available items."""
        print("\n--- Available Items ---")
        for name, data in self.items.items():
            print(f"  {name}: ${data['price']} - {data['description']}")
        print()

    def get_item(self, name):
        """Get item data by name. Returns None if not found."""
        return self.items.get(name.lower())

    def item_exists(self, name):
        """Check if an item exists in the shop."""
        return name.lower() in self.items
Refactored: Helper Functions
def get_valid_budget():
    """Get a valid positive budget from user."""
    while True:
        try:
            budget = float(input("Enter your budget: $"))
            if budget <= 0:
                print("Budget must be positive!")
                continue
            return budget
        except ValueError:
            print("Please enter a valid number")


def get_yes_no(prompt):
    """Get a yes/no response from user."""
    response = input(prompt).strip().lower()
    return response in ['y', 'yes', '1', 'true']


def calculate_discount(price, is_member):
    """Calculate discount based on price and membership."""
    if is_member:
        if price >= 100:
            return price * 0.20
        elif price >= 50:
            return price * 0.10
        else:
            return price * 0.05
    else:
        if price >= 100:
            return price * 0.10
        return 0
Refactored: Main Program
def main():
    """Main shopping program."""
    # Setup
    shop = Shop()
    cart = Cart()

    # Get customer info
    budget = get_valid_budget()
    is_member = get_yes_no("Are you a member? (y/n): ")

    print("\nWelcome to the store!")

    # Shopping loop
    shopping = True
    while shopping:
        shop.display_items()
        cart.show()
        print(f"Budget: ${budget:.2f}")

        # Get purchase
        choice = input("Enter item to buy (or 'done' to checkout): ").strip().lower()

        if choice == 'done':
            shopping = False
            continue

        # Validate item
        if not shop.item_exists(choice):
            print(f"'{choice}' not found in shop!")
            continue

        item = shop.get_item(choice)

        # Check budget
        if item['price'] > budget:
            print("Not enough budget for this item!")
            continue

        # Make purchase
        cart.add_item(choice, item['price'])
        budget -= item['price']

        # Offer undo
        if get_yes_no("Undo this purchase? (y/n): "):
            refund = cart.undo_last()
            budget += refund

    # Checkout
    total = cart.get_total()
    discount = calculate_discount(total, is_member)
    final_price = total - discount

    print("\n--- Checkout ---")
    cart.show()
    print(f"Discount: ${discount:.2f}")
    print(f"Final price: ${final_price:.2f}")


# Run the program
if __name__ == "__main__":
    main()

What Changed

  • Classes group related data and functions - Cart handles cart stuff, Shop handles shop stuff
  • Helper functions - Reusable pieces like get_valid_budget()
  • main() function - Clear entry point, easy to follow
  • Comments - Explain what each section does
  • Consistent naming - is_member, shopping, item

Part 7: Summary 15 min

Bugs We Fixed

Bug Problem Fix
history = Undo Assigns class, not instance history = Undo()
.items(purchase) items is a list, not a method .add(purchase)
Instance inside loop Resets every iteration Create before loop
is ["y", "yes"] Checks identity, not membership in ["y", "yes"]

Readability Principles

  1. Meaningful names - is_shopping not cont
  2. Whitespace - Blank lines between sections
  3. Comments - Explain why, not what
  4. Short functions - One job per function
  5. Classes - Group related data and behavior

Key Concept: Class vs Instance

Class Instance
What is it? A blueprint/template An actual object built from the blueprint
Syntax MyClass MyClass()
Has attributes? Only class attributes Has instance attributes from __init__
Analogy Recipe for cookies An actual cookie you can eat

Gaps Addressed Today

  • Class instantiation - You now understand Class vs Class()
  • Code readability - You have concrete techniques to clean up code
  • Refactoring - You've seen how to restructure messy code into clean classes

Homework: Pet Simulator Download & Complete

Your Mission

Build a virtual pet simulator from scratch! This homework will practice everything we covered today:

  • Class creation - Building a Pet class with attributes and methods
  • Instantiation - Creating pet objects properly with Pet()
  • Readability - Clean code, meaningful names, good structure
  • Game loop - Interactive program using your class

Download: homework_pet.py

Click here to download the homework file

What You'll Build:

Part 1-3: Core Pet Methods
  • Understand the starter Pet class
  • Add play() method - uses energy, increases happiness
  • Add sleep() method - restores energy
Part 4: Time Passes
  • Add time_passes() - hunger increases, energy decreases
  • Warn when pet is hungry, tired, or sad
Part 5: Interactive Game Loop
  • Menu system: feed, play, sleep, status, quit
  • Practice clean code structure
  • Handle user input properly
Part 6-7: Bonus Challenges (Optional)
  • Add pet moods based on stats
  • Create a PetShop class managing multiple pets

Remember: Focus on readability! Use the checklist at the bottom of the homework file to review your code before our next lesson.

Next lesson: Now that you have a solid foundation with classes and readability, we'll explore more OOP concepts: inheritance (creating classes based on other classes) and how to design classes from scratch.

← Back to Blake's Learning Hub