Week 9 of 16

Watch + Read: Inheritance & Special Methods

How classes share behavior — and the double-underscore methods that make Python objects feel native.

Day 42 75 minutes Watch + Read

Day 42 of 80

Today's Videos

Two more Corey Schafer videos, then a reading. Together these cover the most used OOP features in production Python.

# Video Duration Key concept
1 Inheritance and Subclasses ~20 min How child classes inherit from parent classes, method overriding, super()
2 Special (Magic/Dunder) Methods ~15 min __str__, __repr__, __len__, __add__ — making objects behave like built-in types
Also Read

After the videos, skim through the Real Python OOP guide: realpython.com/python3-object-oriented-programming/ — focus specifically on the __init__ and __str__ sections. The rest is reinforcement.

The Magic Methods That Matter Most

There are dozens of dunder methods in Python. You don't need all of them. These six will cover 95% of what you'll ever write.

The essential dunder methods — illustrated with Prompt Python
class Prompt:

    def __init__(self, platform, shot, prompt_text):
        """Runs automatically when you create an instance."""
        self.platform = platform
        self.shot = shot
        self.prompt_text = prompt_text

    def __str__(self):
        """What print(obj) shows — written for humans."""
        return f"[{self.platform}] {self.shot}"

    def __repr__(self):
        """What the debugger/REPL shows — written for developers."""
        return f"Prompt(platform='{self.platform}', shot='{self.shot}')"

    def __len__(self):
        """Makes len(obj) work — returns the character count of the prompt."""
        return len(self.prompt_text)

    def __bool__(self):
        """Makes if obj: work — True if the prompt_text is non-empty."""
        return bool(self.prompt_text)

    def __eq__(self, other):
        """Makes == work — two Prompts are equal if same platform + shot."""
        if not isinstance(other, Prompt):
            return False
        return self.platform == other.platform and self.shot == other.shot


# Now these all work naturally:
p = Prompt("Kling", "aerial shot", "A sweeping drone shot over mountains...")

print(p)        # [Kling] aerial shot
print(repr(p))  # Prompt(platform='Kling', shot='aerial shot')
print(len(p))   # 42 (character count)
if p:           # True — prompt_text is non-empty
    print("Prompt has content")

Why implement these? Because they make your objects feel like built-in Python types. When you can do print(p) and see something useful instead of <__main__.Prompt object at 0x7f...>, your debugging experience improves dramatically.

__str__ vs __repr__: A good rule of thumb — __str__ is for end-user display, __repr__ should look like a constructor call so you could copy-paste it to recreate the object.

Python calls these automatically. You define them once in the class; Python knows to call __len__ when something calls len(obj). That's the power of the protocol.

The __str__ vs __repr__ Rule

Implement both whenever possible. They serve different audiences:

Method Called by Written for Example output
__str__ print(obj), str(obj), f-strings End users, logs, UI [Kling] aerial shot
__repr__ Python REPL, repr(obj), debugger Developers debugging Prompt(platform='Kling', shot='aerial shot')

If you only define one: define __repr__. Python falls back to it when __str__ isn't defined. If you define both, make __str__ friendly and __repr__ precise.

Inheritance Preview

Inheritance lets a child class build on a parent class — inheriting all its methods and adding or overriding specific ones.

Inheritance — KlingPrompt extends Prompt Python
class Prompt:
    """Base class — generic prompt for any platform."""
    def __init__(self, platform, shot, prompt_text):
        self.platform = platform
        self.shot = shot
        self.prompt_text = prompt_text

    def get_system_prompt(self):
        return "You are a helpful video prompt writer."


class KlingPrompt(Prompt):  # inherits from Prompt
    """Kling-specific prompt with camera motion guidance."""

    def __init__(self, shot, prompt_text, camera_motion="static"):
        # super() calls the parent's __init__ — DRY principle
        super().__init__(platform="Kling", shot=shot, prompt_text=prompt_text)
        self.camera_motion = camera_motion  # Kling-specific extra field

    def get_system_prompt(self):  # overrides parent's version
        return f"You are a Kling video expert. Emphasize camera motion ({self.camera_motion})."


# KlingPrompt inherits everything from Prompt
k = KlingPrompt("aerial shot", "Drone ascending slowly...", camera_motion="crane up")
print(k.platform)           # "Kling" — inherited from Prompt.__init__
print(k.get_system_prompt()) # Kling-specific version — overridden
print(isinstance(k, Prompt)) # True — a KlingPrompt IS a Prompt

super() calls the parent class's method. It's how a child class can extend parent behavior without rewriting it. Use it whenever you override __init__ in a subclass.

isinstance(k, Prompt) returns True — inheritance creates an "is-a" relationship. A KlingPrompt is-a Prompt. This matters for type checking and function signatures.

For the Prompt Vault this week: you won't build the full inheritance hierarchy — that's more advanced. But you'll use @classmethod as a different kind of "alternative constructor" which is far more common in real Python code.

End of Day Checklist

Tomorrow

Day 43 is a full build day. You'll write the complete Prompt class from scratch in models.py — with validation, __str__, __repr__, preview(), to_dict(), and from_dict(). All the pieces come together.

Notes

Your local study notes mount here when JavaScript is available.

Protected AI Tutor

Teaching modes mount here when JavaScript is available. The readable course remains available without them.