Warming up the neural circuits...
By the end of this chapter you will:
Reuse through "is-a" has a cost — know it before paying. Inheritance lets you share code between related classes, but deep hierarchies become hard to understand and maintain. Dunder methods let your objects behave like built-in types — printable, comparable, iterable. Master both, and you can design objects that feel native to Python.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal):
def speak(self):
return f"{self.name} barks"
class Cat(Animal):
def speak(self):
return f"{self.name} meows"
dog = Dog("Rex")
print(dog.speak()) # "Rex barks"super() — calling parent methods:
class Animal:
def __init__(self, name, sound):
self.name = name
self.sound = sound
class Dog(Animal):
def __init__(self, name):
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"User('{self.
# Inheritance: Dog IS an Animal
class Dog(Animal):
pass
# Composition: Car HAS an Engine
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self):
self
Dunder methods are Python's protocol for object behavior:
__repr__ → "How does this look in the debugger?"__str__ → "How does this look when printed?"__eq__ → "Are these two objects equal?"__len__ → "How many items?"__getitem__ → "What's at i?"| Mistake | Why it's wrong | Fix |
|---|---|---|
| Deep inheritance (5+ levels) | Hard to understand, fragile | Use composition |
Not calling super().__init__() | Parent attributes not set | Always call super in init |
__repr__ returns same as __str__ | No debug info | __repr__ for developers, __str__ for users |
isinstance(Dog(), Animal) fails | Dog doesn't inherit from Animal | Use inheritance or ABC |
Beginner:
"What's the difference between __repr__ and __str__?"
__repr__is for developers — it should look like valid Python code to recreate the object.__str__is for users — it should be human-readable. When youprint()an object, Python calls__str__. In the debugger, Python calls__repr__.
"What is inheritance?"
Inheritance lets a child class inherit attributes and methods from a parent class. The child can override methods to customize behavior. Use
super()to call parent methods.
Senior:
"When would you choose composition over inheritance?"
Composition is preferred when the relationship is "has-a" (Car has an Engine) rather than "is-a" (Dog is an Animal). Composition is more flexible — you can swap components at runtime, test in isolation, and avoid the tight coupling of inheritance hierarchies.
class Child(Parent):. Use super() to call parent methods.__repr__, __str__, __eq__, __len__ — make objects behave like built-ins.I want to…
├── Inherit → class Child(Parent):
├── Call parent → super().__init__()
├── Print repr → __repr__
├── Print str → __str__
├── Compare == → __eq__
├── Get len → __len__
└── Prefer composition over inheritanceWhat does super().__init__() do?
| Dunder | Called by | Use when |
|---|---|---|
__repr__ | repr() | Developer debugging |
__str__ | print(), str() | User display |
__eq__ | == | Equality comparison |
__len__ | len() | Size/count |
__getitem__ | [] | Indexing |
When to use which:
| Relationship | Use | Example |
|---|---|---|
| "is-a" | Inheritance | Dog is an Animal |
| "has-a" | Composition | Car has an Engine |
| "uses-a" | Parameter | Function takes a Logger |
Rule: Prefer composition. Inheritance is powerful but creates tight coupling.
"What happens when you call len(obj)?"
Python calls
obj.__len__(). If the method isn't defined, it raises TypeError. This is how dunder methods work — Python routes operators and built-in functions to special methods on your objects.