Part of Python for Beginners

Inheritance, Polymorphism, Duck Typing (Beginner Friendly) - Python OOP Tutorial #20

Sandy LaneSandy Lane

Video: Inheritance, Polymorphism, Duck Typing (Beginner Friendly) - Python OOP Tutorial #20 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Python Inheritance, Polymorphism, Duck Typing

class Sub(Base):Sub inherits everything from Base. super().method() calls the parent. Polymorphism: different classes implementing the same interface. Duck typing: "if it has the method, call it" — Python doesn't care about the class.

Inheritance lets you build new classes from existing ones. Polymorphism lets you treat different types uniformly through a shared interface.

Base class and subclass

class Vehicle:
  def __init__(self, make, model, year):
    self.make = make
    self.model = model
    self.year = year

  def describe(self):
    return f"{self.year} {self.make} {self.model}"

  def start(self):
    return f"{self.make} engine started"

A normal class. Now a subclass:

class Car(Vehicle):
  def __init__(self, make, model, year, doors):
    super().__init__(make, model, year)
    self.doors = doors

  def describe(self):
    return f"{super().describe()} ({self.doors}-door)"

class Car(Vehicle):Car inherits everything from Vehicle. The (Vehicle) is the parent.

super().__init__(...) calls the parent's __init__. Without it, the inherited __init__ doesn't run, and you'd have to set self.make, self.model, etc. manually.

Method overriding

class Truck(Vehicle):
  def __init__(self, make, model, year, payload):
    super().__init__(make, model, year)
    self.payload = payload

  def start(self):
    return f"{self.make} diesel engine started"

Truck.start overrides Vehicle.start. When you call truck.start(), Python finds start in Truck first and uses that.

To call the parent's version and extend it:

def describe(self):
  return f"{super().describe()} [{self.payload} ton payload]"

super().describe() runs the inherited method. Then you can add to its result.

What inheritance gives you

Subclasses inherit: - All attributes set by Base.__init__ (if super().__init__ is called). - All methods defined on Base. - All class attributes.

They can: - Add new attributes and methods. - Override existing methods. - Call parent methods with super().

isinstance and issubclass

v = Vehicle("Toyota", "Camry", 2024)
car = Car("Honda", "Civic", 2023, 4)

isinstance(car, Car)         # True
isinstance(car, Vehicle)     # True — Car IS a Vehicle
isinstance(v, Car)           # False

issubclass(Car, Vehicle)     # True
issubclass(Vehicle, Car)     # False

isinstance(obj, Class) checks if obj is an instance of Class (or any subclass). issubclass(A, B) checks the class relationship.

Use isinstance for runtime type checks; prefer it over type(x) == Class (which doesn't match subclasses).

Polymorphism

class Shape:
  def area(self):
    return 0

class Circle(Shape):
  def __init__(self, radius):
    self.radius = radius
  def area(self):
    return math.pi * self.radius ** 2

class Rectangle(Shape):
  def __init__(self, width, height):
    self.width = width
    self.height = height
  def area(self):
    return self.width * self.height

shapes = [Circle(5), Rectangle(4, 6)]
for s in shapes:
  print(s.area())

A function that calls s.area() works on any subclass. Each computes its own area; the caller doesn't care what type it is.

def total_area(shapes):
  return sum(s.area() for s in shapes)

print(total_area(shapes))

This is polymorphism: many forms behind one interface.

Duck typing

Python doesn't require you to inherit from Shape — it just needs the .area() method:

class CustomShape:
  def __init__(self, name, value):
    self.name = name
    self.value = value

  def area(self):
    return self.value

shapes = [Circle(5), Rectangle(4, 6), CustomShape("Mystery", 42)]
for s in shapes:
  print(s.area())

CustomShape doesn't inherit from Shape. Doesn't matter — it has .area(), so it works wherever an area-haver is expected.

"If it walks like a duck and quacks like a duck, it is a duck."

This is duck typing: behavior matters, type doesn't. Python lets you mix unrelated classes that happen to support the same methods.

In typed languages (Java, C#), you'd need a Shape interface that all classes implement. Python skips the formality — it'll just call .area() and trust you.

abstract base classes

If you DO want to enforce "must implement .area()," use abc:

from abc import ABC, abstractmethod

class Shape(ABC):
  @abstractmethod
  def area(self):
    pass

class Circle(Shape):
  def __init__(self, radius):
    self.radius = radius
  def area(self):
    return math.pi * self.radius ** 2

c = Circle(5)             # OK
s = Shape()               # TypeError — can't instantiate abstract class

Subclasses must override area. Forgetting raises an error at instantiation, not at first method call. Lesson 22 dives into abstract classes.

Multiple inheritance

class Flyer:
  def fly(self):
    return f"{self.name} flies"

class Swimmer:
  def swim(self):
    return f"{self.name} swims"

class Duck(Flyer, Swimmer):
  def __init__(self, name):
    self.name = name

d = Duck("Donald")
print(d.fly())     # Donald flies
print(d.swim())    # Donald swims

A class can inherit from multiple parents. Python uses the MRO (Method Resolution Order) to determine which method runs when a name is in multiple parents:

print(Duck.__mro__)
# (<class 'Duck'>, <class 'Flyer'>, <class 'Swimmer'>, <class 'object'>)

Left-to-right, depth-first, with cycle resolution (the C3 algorithm).

Multiple inheritance is powerful but can get tangled. Most projects use it sparingly, often via mixins — small classes that add a single capability.

super() in multiple inheritance

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

class B(A):
  def greet(self):
    super().greet()
    print("B")

class C(A):
  def greet(self):
    super().greet()
    print("C")

class D(B, C):
  def greet(self):
    super().greet()
    print("D")

D().greet()
# A C B D

super() follows the MRO, which is D → B → C → A. The print order is reversed because each subclass calls super first. This is the cooperative multiple-inheritance pattern.

If this seems confusing — that's fair. Most code uses single inheritance.

Composition over inheritance

Inheritance is one way to share code. Composition is often better:

# Inheritance:
class ElectricCar(Car):
  def __init__(self, ..., battery_kwh):
    super().__init__(...)
    self.battery_kwh = battery_kwh

# Composition:
class Battery:
  def __init__(self, kwh):
    self.kwh = kwh

class ElectricCar:
  def __init__(self, ..., battery):
    self.battery = battery

Composition is more flexible. has-a Battery is easier to swap than is-a Vehicle. Reach for inheritance only when there's a true "is-a" relationship.

Common stumbles

Forgetting super().__init__. Parent attributes never get set. The subclass works only as long as you don't depend on inherited state.

type(x) == Class instead of isinstance. Misses subclasses. Use isinstance(x, Class).

Overriding __init__ and changing signature. Calling code that expects the parent signature breaks. Add new params at the end with defaults.

Calling Parent.method(self) directly. Works in single inheritance but bypasses MRO. Use super().method().

Diamond inheritance confusion. D(B, C) where both B and C extend A. Method resolution can surprise. Prefer single inheritance + composition.

Treating duck typing as license to be sloppy. Document the expected interface. "This function accepts any object with .area() and .name" is real documentation.

What's next

Lesson 21: encapsulation, private attributes, @property and setters. _private, __name_mangling, and the property decorator.

Recap

class Sub(Base): for inheritance. super().__init__(...) to call parent constructor. Override methods by redefining; combine with super().method() for "extend, don't replace." Polymorphism: different classes, same method names — caller doesn't care which. Duck typing: Python doesn't require inheritance; it requires the right methods. Use multiple inheritance sparingly; prefer composition for "has-a" relationships.

Next lesson: encapsulation and properties.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.