ASAmol Shukla
Projects
Courses
Prompts
Skills
Contact
Resume
Course Outline
Syllabus Overview

Complete Python Course: From Zero to Professional

Courses/Complete Python Course: From Zero to Professional/Lesson 17: Inheritance & Polymorphism
50 mins lesson duration•9 mins read

Lesson 17: Inheritance & Polymorphism

Deep-dive into inheritance, method overriding, polymorphism, isinstance, and the MRO.

Inheritance — Code Reuse Down the Tree

A child class inherits every attribute and method from its parent, then adds or overrides what it needs. This eliminates duplication and expresses "is-a" relationships: a Dog is an Animal, a Car is a Vehicle.

Inheritance is the difference between copying code and reusing code. When three classes share the same __init__ pattern, you move that pattern to a parent once — and every child inherits it for free.

What You'll Learn in This Lesson

  • Override parent methods and call super()
  • Use polymorphism — one interface, many behaviors
  • Check types with isinstance() and issubclass()
  • Understand the Method Resolution Order (MRO)

Overriding — Child Writes Its Own Version

Redefining a parent method in the child is overriding. The child's version wins for child instances. Call the parent's version with super().method():

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "...generic sound..."

class Dog(Animal):
    def speak(self):                 # OVERRIDE
        return "Woof!"

Override + super() keeps parent logic while extending it — never copy-paste parent code into the child.

The pattern: the child's speak replaces the parent's for Dog instances, while Animal instances keep the generic version. Both methods coexist; which one runs depends on the object's actual class.


Polymorphism — Many Forms, One Interface

Polymorphism lets different classes be used through the same interface. If both Dog and Cat have a .speak() method, a loop can call .speak() on each without caring which class it is:

animals = [Dog("Rocky"), Cat("Whiskers"), Animal("Mystery")]
for animal in animals:
    print(f"{animal.name}: {animal.speak()}")
Rocky: Woof!
Whiskers: Meow!
Mystery: ...generic sound...

"Call the method, let the object decide." Python doesn't check types before calling — it just tries the method. This is called duck typing: if it walks like a duck and quacks like a duck, it's a duck.

Mental model: polymorphism is a universal remote. Same button (the method name), different result per device (per class). The loop doesn't care what each animal is — only that it can speak().

Why polymorphism matters: it lets you write code that works on categories of objects instead of specific ones. Add a new class later (class Parrot(Animal)) and every existing loop, sort, and function that expects .speak() immediately works with it — zero changes.


isinstance() and issubclass()

Function Question Example
isinstance(obj, Class) Is this object an instance? isinstance(dog, Dog) → True
issubclass(Child, Parent) Does it inherit? issubclass(Dog, Animal) → True
print(isinstance(animals[0], Dog))        # True
print(isinstance(animals[0], Animal))     # True — a Dog IS an Animal
print(issubclass(Dog, Animal))            # True

Use isinstance() instead of type(x) == ... — it understands inheritance.

Why type(x) == Dog is wrong: type(dog) == Dog is True for a Dog, but type(dog) == Animal is False even though a Dog is an Animal. isinstance(dog, Animal) correctly returns True. When you check "is this object usable as X", isinstance is the honest answer.


The MRO — Method Resolution Order

When Python calls a method, it searches the class tree in a defined order. ClassName.mro() prints it:

print(Dog.mro())
# [<class 'Dog'>, <class 'Animal'>, <class 'object'>]

Python looks in Dog first, then Animal, then the root object. In multiple inheritance, the search goes left to right across parents.

Mental model: the child looks for a method in its own room first; if not found, it walks up the stairs to the parent's room, then the grandparent's.

Multiple inheritance example:

class A:
    def greet(self):
        return "A"

class B:
    def greet(self):
        return "B"

class C(A, B):   # parents searched left to right
    pass

c = C()
print(c.greet())              # 'A' — A wins
print([cls.__name__ for cls in C.mro()])  # ['C', 'A', 'B', 'object']

Multiple inheritance is powerful but easy to overuse — most real code stays with single inheritance plus composition.


Composition vs Inheritance — Has-a vs Is-a

Relationship Means Use
Inheritance is-a Dog is an Animal
Composition has-a A Car has an Engine
class Engine:
    def start(self):
        return "Vroom!"

class Car:
    def __init__(self):
        self.engine = Engine()     # composition: has-a
    def start(self):
        return self.engine.start()

Prefer composition for flexibility — a car with a different engine doesn't need a new class hierarchy.


Common Mistakes to Avoid

  • Mistake: Copy-pasting parent methods into children — Fix: override + super().
  • Mistake: type(x) == Dog instead of isinstance(x, Dog) — Fix: isinstance respects inheritance.
  • Mistake: Deep inheritance chains (5+ levels) — Fix: prefer composition ("has-a") over inheritance for flexibility.
  • Mistake: Forgetting to call super().__init__() in the child and getting uninitialized attributes — Fix: always initialize the parent.
  • Mistake: Overusing multiple inheritance and creating confusing MROs — Fix: keep it simple; composition usually suffices.

Professional Tips & Tricks

  • Override + super() keeps parent logic while extending it — never copy-paste parent code.
  • Use isinstance() instead of type() == ... when checking class relationships.
  • Prefer composition (has-a) over deep inheritance chains for flexibility.
  • Print ClassName.mro() to debug confusing method lookups.
  • Design for polymorphism: code against the interface (speak, area, save), not specific classes.

Key Takeaways

  • Children inherit and override; super() reaches the parent.
  • Polymorphism: same method name, different behavior per class.
  • isinstance()/issubclass() check relationships.
  • .mro() reveals Python's method search order.
  • Is-a → inheritance; has-a → composition.

Next up: Encapsulation, properties & magic methods.

Interactive Lesson Code Snippet
# Inheritance + polymorphism
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "...generic sound..."

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

# Polymorphism: same interface, different behavior
animals = [Dog("Rocky"), Cat("Whiskers"), Animal("Mystery")]
for a in animals:
    print(f"{a.name}: {a.speak()}")

# Type checks
print("Rocky is a Dog:", isinstance(animals[0], Dog))
print("Dog is Animal subclass:", issubclass(Dog, Animal))

# Method resolution order
print("Dog MRO:", [c.__name__ for c in Dog.mro()])
Language: python

Lesson Code (Python)

# Inheritance + polymorphism
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "...generic sound..."

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

# Polymorphism: same interface, different behavior
animals = [Dog("Rocky"), Cat("Whiskers"), Animal("Mystery")]
for a in animals:
    print(f"{a.name}: {a.speak()}")

# Type checks
print("Rocky is a Dog:", isinstance(animals[0], Dog))
print("Dog is Animal subclass:", issubclass(Dog, Animal))

# Method resolution order
print("Dog MRO:", [c.__name__ for c in Dog.mro()])

Console Output

Rocky: Woof!
Whiskers: Meow!
Mystery: ...generic sound...
Rocky is a Dog: True
Dog is Animal subclass: True
Dog MRO: ['Dog', 'Animal', 'object']

Code Visualization Tips

  • 🧠Draw the class tree and trace a method call walking up until it finds the first implementation.
  • 🧠For polymorphism, picture a universal remote that works on every device — same button, different result.
  • 🧠Use Dog.mro() to literally print the search order Python will follow.

Professional Tips & Tricks

  • ⚡Override + super() keeps parent logic while extending it — never copy-paste parent code.
  • ⚡Use isinstance() instead of type() == ... when checking class relationships.
  • ⚡Prefer composition (has-a) over deep inheritance chains for flexibility.

Python Code Judge & Practice Arena

LeetCode Style

Run real Python 3.12 WebAssembly code directly in your browser against automated test suites.

Solved:0 / 20
0 / 370 XP
Challenges:
Problem 1 of 20

Problem 1: Polymorphic Shapes 1

Easy+10 XP
Implement base class `Shape_1` with `area()` and child classes `Rectangle_1(w, h)` and `Circle_1(r)` that calculate their respective areas rounded to 2 decimal places.
Sample Test Cases:
Input: Rectangle_1(4, 5).area()
Expected: 20.0
Input: Circle_1(3).area()
Expected: 28.27
main.pyPython 3.12 (WASM)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Press Run Code to test or Submit to verify test cases

Test Your Knowledge

Instant feedback

Quick Check: Lesson 17: Inheritance, Polymorphism, `super()` & MRO

1 / 20
What does `super().__init__()` do in a derived class constructor? class Base: def __init__(self, id): self.id = id class Child(Base): def __init__(self, id, name): super().__init__(id) self.name = name

Up next · Continue learning

Encapsulation, Properties & Magic Methods

Private attributes, @property for smart access, and dunder methods that customize objects.

10 mins read55 mins
Start next lesson
Previous: Classes, Instances & InheritanceNext: Encapsulation, Properties & Magic Methods
Made withbyAmol Shukla·amolshukla.online
ASAmol Shukla

AI Developer, Trainer & Agentic AI Expert building practical learning systems and real-world AI applications.

Explore

  • Projects
  • Courses
  • Prompts
  • Skills
  • Contact
  • Experience
  • Blogs

Connect

  • Resume
  • Contact
© 2026 Amol Shukla·Created withbyamolshukla.online
Back to top