DocsHub
Object-Oriented Programming

Inheritance

Learn how classes can inherit from other classes in Python — single inheritance, multiple inheritance, and super().

Inheritance

You have a fully working BankAccount class. Now the bank wants to offer different account types — a savings account that earns interest, and a loan account that tracks debt. Both are bank accounts — they have an owner, a balance, deposits, and withdrawals. But each has extra behavior specific to its type.

Without inheritance, you would copy the entire BankAccount class and add the new features. That means two copies of the same code — and when something changes, you have to update both.

Inheritance solves this. A child class inherits everything from a parent class and adds or changes only what is different.

BankAccount (parent)
├── SavingsAccount (child) — adds interest
└── LoanAccount (child)    — adds debt tracking

Basic inheritance

To inherit from a class, put the parent class name in parentheses:

class SavingsAccount(BankAccount):
    pass

SavingsAccount now has everything BankAccount has — __init__, deposit, withdraw, check — without writing any of it again:

class BankAccount:
    bank_name = "HBL Bank"

    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            print(f"Deposited {amount:,} PKR → Balance: {self.balance:,} PKR")
        else:
            print("Invalid amount.")

    def withdraw(self, amount):
        if amount <= 0:
            print("Invalid amount.")
        elif amount > self.balance:
            print(f"Insufficient balance. Available: {self.balance:,} PKR")
        else:
            self.balance -= amount
            print(f"Withdrawn {amount:,} PKR → Balance: {self.balance:,} PKR")

    def check(self):
        print(f"Bank:    {BankAccount.bank_name}")
        print(f"Owner:   {self.owner}")
        print(f"Balance: {self.balance:,} PKR")
        print("-" * 35)


class SavingsAccount(BankAccount):
    pass


# SavingsAccount inherits everything from BankAccount
account = SavingsAccount("Ahmad", 10000)
account.deposit(5000)
account.check()

Output:

Deposited 5,000 PKR → Balance: 15,000 PKR
Bank:    HBL Bank
Owner:   Ahmad
Balance: 15,000 PKR
-----------------------------------

SavingsAccount did not define deposit or check — it inherited them from BankAccount.


super() — calling the parent

When a child class needs its own __init__ to add extra data, use super() to call the parent's __init__ first. This ensures the parent sets up its own data before the child adds its own:

class SavingsAccount(BankAccount):
    def __init__(self, owner, balance, interest_rate):
        super().__init__(owner, balance)   # let BankAccount handle owner and balance
        self.interest_rate = interest_rate  # then add our own data

    def apply_interest(self):
        interest = self.balance * (self.interest_rate / 100)
        self.balance += interest
        print(f"Interest applied: {interest:,.2f} PKR")
        print(f"New balance: {self.balance:,.2f} PKR")

    def check(self):
        print(f"Bank:          {BankAccount.bank_name}")
        print(f"Account type:  Savings")
        print(f"Owner:         {self.owner}")
        print(f"Balance:       {self.balance:,} PKR")
        print(f"Interest rate: {self.interest_rate}%")
        print("-" * 35)
savings = SavingsAccount("Ahmad", 10000, interest_rate=5)

savings.check()
savings.deposit(5000)
savings.apply_interest()
savings.check()

Output:

Bank:          HBL Bank
Account type:  Savings
Owner:         Ahmad
Balance:       10,000 PKR
Interest rate: 5%
-----------------------------------
Deposited 5,000 PKR → Balance: 15,000 PKR
Interest applied: 750.00 PKR
New balance: 15,750.00 PKR
Bank:          HBL Bank
Account type:  Savings
Owner:         Ahmad
Balance:       15,750 PKR
Interest rate: 5%
-----------------------------------

Always call super().__init__() at the start of a child's __init__. This gives the parent a chance to set up its own instance variables before the child adds its own. Skipping it means the parent's setup never runs — and all the parent's methods that rely on those variables will break.


Method overriding

A child class can override a parent method — replace it with its own version. When you call the method on a child object, Python uses the child's version, not the parent's:

class LoanAccount(BankAccount):
    def __init__(self, owner, loan_amount, interest_rate):
        super().__init__(owner, balance=0)   # loan accounts start with 0 balance
        self.loan_amount = loan_amount
        self.interest_rate = interest_rate
        self.amount_paid = 0

    def pay(self, amount):
        if amount <= 0:
            print("Payment must be positive.")
            return
        self.amount_paid += amount
        remaining = self.loan_amount - self.amount_paid
        print(f"Paid {amount:,} PKR. Remaining loan: {max(remaining, 0):,} PKR")

    def apply_interest(self):
        interest = self.loan_amount * (self.interest_rate / 100)
        self.loan_amount += interest
        print(f"Interest added: {interest:,.2f} PKR")
        print(f"Total loan now: {self.loan_amount:,.2f} PKR")

    def check(self):
        remaining = self.loan_amount - self.amount_paid
        print(f"Bank:          {BankAccount.bank_name}")
        print(f"Account type:  Loan")
        print(f"Owner:         {self.owner}")
        print(f"Loan amount:   {self.loan_amount:,} PKR")
        print(f"Amount paid:   {self.amount_paid:,} PKR")
        print(f"Remaining:     {max(remaining, 0):,} PKR")
        print(f"Interest rate: {self.interest_rate}%")
        print("-" * 35)
loan = LoanAccount("Sara", loan_amount=100000, interest_rate=10)

loan.check()
loan.pay(20000)
loan.pay(15000)
loan.apply_interest()
loan.check()

Output:

Bank:          HBL Bank
Account type:  Loan
Owner:         Sara
Loan amount:   100,000 PKR
Amount paid:   0 PKR
Remaining:     100,000 PKR
Interest rate: 10%
-----------------------------------
Paid 20,000 PKR. Remaining loan: 80,000 PKR
Paid 15,000 PKR. Remaining loan: 65,000 PKR
Interest added: 10,000.00 PKR
Total loan now: 110,000.00 PKR
Bank:          HBL Bank
Account type:  Loan
Owner:         Sara
Loan amount:   110,000 PKR
Amount paid:   35,000 PKR
Remaining:     75,000 PKR
Interest rate: 10%
-----------------------------------

check() is defined in BankAccount, SavingsAccount, and LoanAccount — each with its own version. Python always runs the most specific one.


isinstance() and issubclass()

Check if an object is an instance of a class or its parents:

savings = SavingsAccount("Ahmad", 10000, 5)
loan    = LoanAccount("Sara", 100000, 10)

print(isinstance(savings, SavingsAccount))   # True
print(isinstance(savings, BankAccount))      # True — because it inherits
print(isinstance(savings, LoanAccount))      # False

print(issubclass(SavingsAccount, BankAccount))   # True
print(issubclass(LoanAccount, BankAccount))      # True
print(issubclass(BankAccount, SavingsAccount))   # False — reversed

Calling the parent method alongside the child

Sometimes you want to run the parent's method AND add something extra. Use super() inside the overridden method:

class SavingsAccount(BankAccount):
    def __init__(self, owner, balance, interest_rate):
        super().__init__(owner, balance)
        self.interest_rate = interest_rate

    def deposit(self, amount):
        super().deposit(amount)   # run BankAccount's deposit first
        print(f"(Savings account — interest rate: {self.interest_rate}%)")
savings = SavingsAccount("Ahmad", 10000, 5)
savings.deposit(3000)

Output:

Deposited 3,000 PKR → Balance: 13,000 PKR
(Savings account — interest rate: 5%)

Multiple inheritance

Python allows a class to inherit from more than one parent. Use it carefully — it adds complexity.

A real example — a premium account that is both a savings account and has a credit card feature:

class CreditCard:
    def __init__(self, credit_limit):
        self.credit_limit = credit_limit
        self.credit_used = 0

    def charge(self, amount):
        if self.credit_used + amount > self.credit_limit:
            print(f"Credit limit exceeded. Available: {self.credit_limit - self.credit_used:,} PKR")
        else:
            self.credit_used += amount
            print(f"Charged {amount:,} PKR. Credit used: {self.credit_used:,}/{self.credit_limit:,} PKR")

    def pay_credit(self, amount):
        self.credit_used = max(0, self.credit_used - amount)
        print(f"Credit paid. Remaining credit used: {self.credit_used:,} PKR")


class PremiumAccount(SavingsAccount, CreditCard):
    def __init__(self, owner, balance, interest_rate, credit_limit):
        SavingsAccount.__init__(self, owner, balance, interest_rate)
        CreditCard.__init__(self, credit_limit)

    def check(self):
        print(f"Bank:          {BankAccount.bank_name}")
        print(f"Account type:  Premium")
        print(f"Owner:         {self.owner}")
        print(f"Balance:       {self.balance:,} PKR")
        print(f"Interest rate: {self.interest_rate}%")
        print(f"Credit limit:  {self.credit_limit:,} PKR")
        print(f"Credit used:   {self.credit_used:,} PKR")
        print("-" * 35)
premium = PremiumAccount("Omar", 50000, interest_rate=7, credit_limit=20000)

premium.check()
premium.deposit(10000)
premium.apply_interest()
premium.charge(5000)
premium.charge(18000)   # exceeds limit
premium.pay_credit(3000)
premium.check()

Output:

Bank:          HBL Bank
Account type:  Premium
Owner:         Omar
Balance:       50,000 PKR
Interest rate: 7%
Credit limit:  20,000 PKR
Credit used:   0 PKR
-----------------------------------
Deposited 10,000 PKR → Balance: 60,000 PKR
Interest applied: 4,200.00 PKR
New balance: 64,200.00 PKR
Charged 5,000 PKR. Credit used: 5,000/20,000 PKR
Credit limit exceeded. Available: 15,000 PKR
Credit paid. Remaining credit used: 2,000 PKR
Bank:          HBL Bank
Account type:  Premium
Owner:         Omar
Balance:       64,200 PKR
Interest rate: 7%
Credit limit:  20,000 PKR
Credit used:   2,000 PKR
-----------------------------------

MRO — Method Resolution Order

When a class inherits from multiple parents, Python uses the MRO to decide which method to call when there is a conflict. You can see the MRO with __mro__:

print(PremiumAccount.__mro__)

Output:

(<class 'PremiumAccount'>, <class 'SavingsAccount'>, <class 'BankAccount'>, <class 'CreditCard'>, <class 'object'>)

Python searches left to right. PremiumAccount first, then SavingsAccount, then BankAccount, then CreditCard, then object. The first match wins.

Multiple inheritance gets complicated quickly. Use it only when it genuinely makes sense — like mixing in a clearly separate feature. If you find yourself struggling with MRO conflicts, it is usually a sign to restructure your code.


The complete picture

# parent
account   = BankAccount("Ali", 5000)

# children — inherit everything, add their own
savings   = SavingsAccount("Ahmad", 10000, interest_rate=5)
loan      = LoanAccount("Sara", 100000, interest_rate=10)
premium   = PremiumAccount("Omar", 50000, interest_rate=7, credit_limit=20000)

# all have check() — each runs its own version
account.check()
savings.check()
loan.check()
premium.check()

# all have deposit() — inherited from BankAccount
account.deposit(1000)
savings.deposit(1000)

# only savings and premium have apply_interest()
savings.apply_interest()
premium.apply_interest()

# only loan has pay()
loan.pay(10000)

# only premium has charge()
premium.charge(3000)

Summary

ConceptExample
Inherit from a classclass Child(Parent):
Call parent __init__super().__init__(...)
Override a methodDefine the same method in the child
Call parent method toosuper().method_name()
Multiple inheritanceclass Child(Parent1, Parent2):
Check instance typeisinstance(obj, ClassName)
Check class hierarchyissubclass(Child, Parent)
See method orderClassName.__mro__

On this page