Inheritance

Python · Lesson 35 of 56 6 min read Updated

Many things in the real world are special versions of something more general. A sleeper ticket and an AC ticket are both train tickets; a UPI payment and a card payment are both payments. Inheritance lets you write the shared part once in a general class and then create special versions that reuse it and change only what is different.

In this lesson you will build subclasses, override methods, call the parent with super(), check types with isinstance(), and force subclasses to provide methods with abstract base classes.

Creating a subclass

The general class is called the parent, base or superclass. A class that inherits from it is the child or subclass. To inherit, put the parent's name in brackets after the class name. The child automatically gets every attribute and method of the parent.

PythonRun it yourself
class Ticket:
    rate_per_km = 0.5

    def __init__(self, passenger, distance_km):
        self.passenger = passenger
        self.distance_km = distance_km

    def fare(self):
        return round(self.distance_km * self.rate_per_km)

    def summary(self):
        kind = type(self).__name__
        return f"{kind} for {self.passenger}: ₹{self.fare()}"

class SleeperTicket(Ticket):
    rate_per_km = 0.9

class ACTicket(Ticket):
    rate_per_km = 2.4

trip = [Ticket("Aarav", 300), SleeperTicket("Priya", 300), ACTicket("Kabir", 300)]
for ticket in trip:
    print(ticket.summary())
Output
Ticket for Aarav: ₹150
SleeperTicket for Priya: ₹270
ACTicket for Kabir: ₹720

SleeperTicket and ACTicket have no __init__, fare() or summary() of their own. They inherit all three from Ticket and only change the class attribute rate_per_km. When fare() reads self.rate_per_km, Python looks at the object's own class first, so each ticket uses its own rate.

The loop also shows polymorphism: the same call, ticket.summary(), works on different classes, and each object answers in its own way.

Overriding methods and calling super()

A subclass can override a method by defining one with the same name. Often you do not want to replace the parent's work completely, only add to it. super() gives you access to the parent's version, so you can call it and then do the extra part.

The most common place for this is __init__, when a subclass needs extra attributes:

PythonRun it yourself
class Ticket:
    rate_per_km = 0.5

    def __init__(self, passenger, distance_km):
        self.passenger = passenger
        self.distance_km = distance_km

    def fare(self):
        return round(self.distance_km * self.rate_per_km)

class TatkalTicket(Ticket):
    def __init__(self, passenger, distance_km, tatkal_charge):
        super().__init__(passenger, distance_km)
        self.tatkal_charge = tatkal_charge

    def fare(self):
        return super().fare() + self.tatkal_charge

normal = Ticket("Meera", 500)
urgent = TatkalTicket("Rohan", 500, 175)
print(normal.fare(), urgent.fare())
print(urgent.passenger, urgent.tatkal_charge)
Output
250 425
Rohan 175

super().__init__(passenger, distance_km) lets the parent set passenger and distance_km, so that code is not repeated. Then the child adds tatkal_charge. In fare(), super().fare() calculates the normal fare and the child adds the extra charge on top.

Checking types with isinstance()

isinstance(obj, SomeClass) returns True if the object belongs to that class or any of its subclasses. issubclass(Child, Parent) asks the same question about classes.

PythonRun it yourself
class Ticket:
    pass

class TatkalTicket(Ticket):
    pass

t = TatkalTicket()
print(isinstance(t, TatkalTicket))
print(isinstance(t, Ticket))
print(isinstance(Ticket(), TatkalTicket))
print(issubclass(TatkalTicket, Ticket))
print(issubclass(Ticket, object))
Output
True
True
False
True
True

The last line shows that every class in Python inherits from the built-in class object, even if you do not write it. That is where default behaviour such as printing <__main__.Ticket object at 0x...> comes from.

Prefer isinstance() over type(t) == Ticket, because it respects inheritance: a Tatkal ticket is a ticket.

Abstract base classes

Sometimes the parent class is only a template, and it makes no sense to create objects from it directly. A general "payment" has no way to pay; only UPI or card payments do. The abc module (abstract base classes) lets you mark such methods with @abstractmethod. Python then refuses to create an object of any class that has not provided them.

PythonRun it yourself
from abc import ABC, abstractmethod

class Payment(ABC):
    def __init__(self, amount):
        self.amount = amount

    @abstractmethod
    def pay(self):
        """Every payment type must say how it pays."""

class UpiPayment(Payment):
    def __init__(self, amount, upi_id):
        super().__init__(amount)
        self.upi_id = upi_id

    def pay(self):
        return f"Paid ₹{self.amount} to {self.upi_id} by UPI"

class CardPayment(Payment):
    def pay(self):
        return f"Paid ₹{self.amount} by card"

for p in [UpiPayment(349, "chaiwala@okbank"), CardPayment(1299)]:
    print(p.pay())

try:
    Payment(100)
except TypeError as error:
    print("Error:", error)
Output
Paid ₹349 to chaiwala@okbank by UPI
Paid ₹1299 by card
Error: Can't instantiate abstract class Payment without an implementation for abstract method 'pay'

Abstract classes act like a contract. If someone adds a WalletPayment class next month and forgets to write pay(), they get a clear error the moment they create one, not a confusing bug later.

Multiple inheritance and the MRO

A class can inherit from more than one parent: class C(A, B):. Python then needs a rule for which parent to search first. That order is called the method resolution order (MRO), and you can see it with ClassName.__mro__. Python searches the class itself, then the parents from left to right, and object last.

Multiple inheritance is mostly used for small mixins: classes that add one feature, such as a method that converts an object to JSON. Keep it simple; deep or tangled hierarchies are hard to follow.

Common mistakes

  • Forgetting super().__init__(). If a child defines its own __init__ and does not call the parent's, the parent's attributes are never set, and you get an AttributeError later.
  • Copying parent code into the child. If you paste a parent method and change one line, use super() and add only the new part.
  • Using inheritance for "has a". An Order should not inherit from list; give it an items list attribute.
  • Comparing types with ==. type(obj) == Parent is False for subclasses; use isinstance().

Key points

  • A subclass inherits all attributes and methods of its parent: class Child(Parent):.
  • Override a method by defining it again in the child; call the parent's version with super().
  • Call super().__init__(...) when a child adds its own __init__.
  • isinstance() and issubclass() respect inheritance; every class inherits from object.
  • ABC and @abstractmethod stop objects being created until subclasses implement required methods.
  • Use inheritance for "is a" relationships and composition for "has a".

Exercise

Create a base class Employee with name and monthly_salary, and a method annual_pay() that returns 12 times the salary. Create a subclass Manager that also takes bonus, calls super().__init__(), and overrides annual_pay() to add the bonus using super(). Create one employee and one manager from a Pune IT company and print each person's name and annual pay.

Show solution
Python
class Employee:
    def __init__(self, name, monthly_salary):
        self.name = name
        self.monthly_salary = monthly_salary

    def annual_pay(self):
        return self.monthly_salary * 12

class Manager(Employee):
    def __init__(self, name, monthly_salary, bonus):
        super().__init__(name, monthly_salary)
        self.bonus = bonus

    def annual_pay(self):
        return super().annual_pay() + self.bonus

staff = [Employee("Ananya", 65000), Manager("Farhan", 120000, 200000)]
for person in staff:
    print(f"{person.name}: ₹{person.annual_pay():,}")
Output
Ananya: ₹780,000
Farhan: ₹1,640,000

Manager reuses the parent's salary calculation through super().annual_pay() and only adds the bonus, so the yearly formula lives in one place.

Quick Quiz

1. How do you make Car inherit from Vehicle?

2. What does super().__init__(name) do inside a child's __init__?

3. class Tatkal(Ticket) exists and t = Tatkal(). What is isinstance(t, Ticket)?

4. What happens when you create an object of a class that still has an abstract method?

Learn Python with a mentor

Free lessons get you started. Our Python Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call