How classes share behavior — and the double-underscore methods that make Python objects feel native.
Day 42 of 80
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 |
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.
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.
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.
__str__ vs __repr__ RuleImplement 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 lets a child class build on a parent class — inheriting all its methods and adding or overriding specific ones.
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.
__init__ and __str__ sections__str__ is for vs __repr__super() does in a child class's __init____str__ and __repr__ and confirmed they workDay 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.
Your local study notes mount here when JavaScript is available.
Teaching modes mount here when JavaScript is available. The readable course remains available without them.