C.W.K.
Stream
Lesson 05 of 07 · published

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'

External links

Exercise

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.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.