20  Objects

20.1 The Wall

AI used class and self everywhere and you had no idea why. It wrote self.name, self.history, self.respond(), every variable and function had self stuck on the front. You asked AI why and got a lecture about “object-oriented programming” and “encapsulation” that did not help you understand the actual code.

You tried changing self.name to just name and everything broke. You did not know why self was necessary or what it represented.

This chapter fixes that.

20.2 Thinking Session

20.2.1 Getting Oriented

NoteThinking Session Prompt

Explain Python classes and objects to me without jargon. I see AI using class Chatbot: and self.name constantly. What does self actually mean? Why can I not just use regular variables and functions? Give me a concrete example where a class is clearly better than functions.

Your AI should explain: a class bundles data (attributes) and behaviour (methods) together. self refers to the specific instance, if you create two chatbots, self.name is different for each one. Without classes, you would need separate variables for each chatbot’s name, history, and settings. If your AI starts with abstract theory about “blueprints” and “objects,” ask for a concrete example first.

20.2.2 Go Deeper

NoteThinking Session Prompt

Walk me through the parts of a Python class: __init__, methods, attributes, and the self parameter. Why does every method take self as the first parameter? What is __init__ and when does it run? Show me with a simple example, not a theoretical one.

TipWhat to Look For

__init__ is called automatically when you create an instance (bot = Chatbot("PyBot")). self is the instance being created or operated on. Python passes it automatically. Attributes set in __init__ (like self.name = name) persist on the object. Methods are functions that operate on the object’s data.

NoteThinking Session Prompt

What is inheritance in Python? AI sometimes writes class EnhancedBot(Chatbot):, what does the parentheses part mean? When would I use inheritance versus just adding methods to the original class?

20.2.3 Challenge It

NoteThinking Session Prompt

What is wrong with this class?

class Counter:
    count = 0

    def increment(self):
        count += 1

    def get_count():
        return count
TipWhat to Look For

Three bugs: (1) increment uses count instead of self.count. It tries to modify a local variable that does not exist. (2) get_count is missing self parameter. It cannot be called as a method. (3) count = 0 at the class level is a class variable shared by all instances. It should be self.count = 0 in __init__ for per-instance counting.

20.2.4 What You Should Have Learned

  • A class bundles data (attributes) and behaviour (methods)
  • self refers to the specific instance
  • __init__ sets up the instance when created
  • Every method takes self as its first parameter
  • Inheritance lets you extend existing classes
  • Use classes when you need multiple instances with their own state

20.3 The Gap

You now understand why AI uses classes. When you see class Chatbot: with self.history and self.respond(), you know that self.history belongs to that specific chatbot instance, and respond() operates on that instance’s data. Classes are how AI structures any program that needs multiple instances of the same thing.

In the final Building Session, you will refactor your entire chatbot into a class.

20.4 Building Session

20.4.1 The Spec

Refactor your chatbot into a class:

  • A Chatbot class with attributes for name, version, history, keywords, responses
  • Methods: greet(), classify(), respond(), save_history(), load_history()
  • An __init__ method that sets up all state
  • A run() method that contains the main loop
  • The entry point creates an instance and calls run()

20.4.2 Prompt It

NoteBuilding Session Prompt

Refactor my chatbot to v2.0 using a class. Create a Chatbot class that:

  • init(self, name, version) sets up: self.name, self.version, self.history (loaded from file), self.responses (the dictionary), self.keywords (the dictionary), self.message_count, self.debug_mode
  • greet(self), displays the welcome screen
  • get_user_name(self), asks for and validates the user’s name
  • classify(self, text), classifies input using self.keywords
  • respond(self, user_input), returns (response, should_exit)
  • save_history(self), saves self.history to JSON
  • load_history(self), loads history from JSON
  • run(self). The main conversation loop

Entry point: bot = Chatbot(“PyBot”, “2.0”) bot.run()

20.4.3 Read the Code

Your AI will produce something like this:

"""PyBot v2.0: Object-oriented chatbot."""
import json
import random


class Chatbot:
    """A terminal chatbot with memory and
    pattern matching."""

    def __init__(self, name, version):
        """Initialise the chatbot."""
        self.name = name
        self.version = version
        self.message_count = 0
        self.debug_mode = False
        self.history = self.load_history()
        self.responses = {
            "greeting": ["Hello!", "Hi there!"],
            "farewell": ["Goodbye!", "See you!"],
            "question": ["Good question!"],
            "default": ["Interesting!", "Go on..."],
        }
        self.keywords = {
            "hello": "greeting",
            "hi": "greeting",
            "bye": "farewell",
        }

    def classify(self, text):
        """Classify input into a response category."""
        words = text.lower().split()
        for word in words:
            if word in self.keywords:
                return self.keywords[word]
        if "?" in text:
            return "question"
        return "default"

    def respond(self, user_input):
        """Generate a response."""
        category = self.classify(user_input)
        responses = self.responses.get(
            category, self.responses["default"]
        )
        return random.choice(responses), category == "farewell"

    def load_history(self):
        """Load conversation history from file."""
        try:
            with open("chat_history.json") as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            return []

    def save_history(self):
        """Save conversation history to file."""
        with open("chat_history.json", "w") as f:
            json.dump(self.history, f, indent=2)

    def run(self):
        """Main conversation loop."""
        print(f"Hello! I'm {self.name} v{self.version}.")
        user_name = input(f"{self.name}: Your name? ").strip()
        print(f"Nice to meet you, {user_name}!")

        try:
            while True:
                user_input = input(f"{user_name}: ").strip()
                if not user_input:
                    continue

                self.message_count += 1
                response, should_exit = self.respond(user_input)
                print(f"{self.name}: {response}")

                self.history.append({
                    "user": user_input,
                    "bot": response,
                })

                if should_exit:
                    break
        except KeyboardInterrupt:
            print(f"\n{self.name}: Interrupted!")
        finally:
            self.save_history()


if __name__ == "__main__":
    bot = Chatbot("PyBot", "2.0")
    bot.run()
TipWhat to Notice

All state lives on self: self.history, self.keywords, self.message_count. Methods access this shared state through self. You could create two chatbots with different names: bot1 = Chatbot("PyBot", "2.0") and bot2 = Chatbot("Helper", "1.0"). Each has its own state. The if __name__ == "__main__" guard means this file works both as a script and as an importable module.

20.4.4 Stretch It

NoteBuilding Session Prompt

Create an EnhancedBot class that inherits from Chatbot and adds a mood system. Override the respond() method to adjust responses based on self.mood. Call super().respond() to get the base response, then modify it.

20.5 Your Chatbot So Far

  • Ch 1-18: Full features split across functions and modules
  • Ch 19: Package structure
  • Ch 20: Refactored into a Chatbot class with all features

Your chatbot journey is complete. You started with 10 lines of print statements and built a full-featured conversational program with pattern matching, persistent memory, error handling, tests, and a clean class-based architecture.

More importantly, you built it through conversation with AI, directing it, questioning it, and understanding every line it produced.

20.6 Quick Reference

# Define a class
class Dog:
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed

    def speak(self):
        return f"{self.name} says Woof!"

# Create instances
dog1 = Dog("Rex", "Labrador")
dog2 = Dog("Bella", "Poodle")
print(dog1.speak())    # "Rex says Woof!"

# Inheritance
class Puppy(Dog):
    def __init__(self, name, breed, age_months):
        super().__init__(name, breed)
        self.age_months = age_months

    def speak(self):
        return f"{self.name} says Yap!"

# Special methods
class Item:
    def __str__(self):
        return "display string"
    def __repr__(self):
        return "debug string"
    def __len__(self):
        return 42