Inheritance, super(), and Composition over Inheritance
~22 min · inheritance, super, composition, subclass
Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
Inheritance — extending without copying
A class can inherit from another — class Dog(Animal):. The subclass gets all the parent's attributes and methods, and can add or override. Used well, inheritance models "is-a" relationships: a Dog is an Animal. Used badly, it's a tangle of behavior overrides that nobody can follow.
super() — calling the parent's version
When you override a method, you often want to do what the parent does and then add your own logic. super().method(...) calls the parent's version. The most common use: super().__init__(...) in a subclass's __init__ to let the parent initialize its part before you initialize yours.
Method resolution — the rules in single inheritance
For single inheritance, the rule is simple: Python checks the instance's class, then its parent, then the parent's parent, up to object. The first match wins. Multiple inheritance complicates this with the C3 linearization algorithm — that's the next track. For now, single inheritance covers most real-world cases.
When to use composition instead
If you find yourself writing class Car(Engine):, stop. A car has-a engine; it isn't an engine. The right shape: self.engine = Engine(). Composition keeps each class focused on one responsibility, makes substitution easy, and avoids the deep inheritance trees that fragment-based design produces. Modern Python style strongly prefers composition.
Principle: "Favor composition over inheritance." Inheritance is correct when the relationship is genuinely "is-a" AND the parent's behavior is what you want to extend (not customize-by-overriding-everything). Otherwise — composition. Pippa's adapters are composed, not inherited; the only inheritance is the narrow ABC for the streaming API contract.
Code
Inheritance and super()·python
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # let Animal initialize its part
self.breed = breed
def speak(self): # override
return f"{self.name} ({self.breed}) barks"
d = Dog("Rex", "Lab")
print(d.name) # 'Rex' — inherited from Animal's __init__
print(d.breed) # 'Lab'
print(d.speak()) # 'Rex (Lab) barks'
Calling super().method() — extending, not replacing·python
class Logger:
def log(self, msg):
print(f"[LOG] {msg}")
class TimestampedLogger(Logger):
def log(self, msg):
import datetime
stamped = f"{datetime.datetime.now().isoformat()}: {msg}"
super().log(stamped) # call parent's version with the modified msg
tl = TimestampedLogger()
tl.log("hello")
# [LOG] 2026-05-02T12:34:56.789: hello
isinstance and issubclass·python
class Animal: pass
class Dog(Animal): pass
class Lab(Dog): pass
l = Lab()
print(isinstance(l, Lab)) # True
print(isinstance(l, Dog)) # True — Lab inherits Dog
print(isinstance(l, Animal)) # True — and Animal
print(issubclass(Lab, Animal)) # True
print(issubclass(Dog, Lab)) # False — Dog is parent, not subclass
# isinstance accepts a tuple of types
print(isinstance(l, (Lab, str))) # True
Composition over inheritance — the same problem two ways·python
# Inheritance — Car IS an Engine. Wrong relationship.
class Engine:
def start(self):
return "vroom"
class CarBad(Engine): # bad: a Car is not an Engine
def drive(self):
return self.start() + " go"
# Composition — Car HAS an Engine. Right relationship.
class CarGood:
def __init__(self):
self.engine = Engine() # composition
def drive(self):
return self.engine.start() + " go"
c = CarGood()
print(c.drive())
# Easy to swap engines
class ElectricMotor:
def start(self):
return "hum"
c.engine = ElectricMotor() # swap implementation, no inheritance change
print(c.drive()) # 'hum go'
Define Shape with __init__(name) and a method describe() that returns f"{self.name} shape". Subclass it with Rectangle(width, height) and Circle(radius). Each subclass: (a) calls super().__init__ with an appropriate name, (b) overrides describe to include dimensions. Then build a class Drawing that has-a list of shapes (composition); add an add(shape) method and a show() method that prints each shape's describe(). Test with at least two of each shape.
Progress
Progress is local-only — sign in to sync across devices.