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 trackingBasic inheritance
To inherit from a class, put the parent class name in parentheses:
class SavingsAccount(BankAccount):
passSavingsAccount 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 — reversedCalling 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
| Concept | Example |
|---|---|
| Inherit from a class | class Child(Parent): |
Call parent __init__ | super().__init__(...) |
| Override a method | Define the same method in the child |
| Call parent method too | super().method_name() |
| Multiple inheritance | class Child(Parent1, Parent2): |
| Check instance type | isinstance(obj, ClassName) |
| Check class hierarchy | issubclass(Child, Parent) |
| See method order | ClassName.__mro__ |