Python Inheritance¶
- Inheritance is a fundamental concept in object-oriented programming that allows one class (child) to reuse the properties and behaviors of another class (parent). This helps avoid code repetition and promotes maintainability.
Key Concepts¶
Inheritance enables a class to acquire attributes and methods from another class.
The base class (also called parent or superclass) provides the common functionality.
The derived class (also called child or subclass) extends or overrides functionality from the base class.
Syntax¶
class BaseClass:
Body of base class
class DerivedClass(BaseClass):
Body of derived class
Defining a Parent Class¶
- Any Python class can serve as a parent class and syntax is the same as creating any other class in python.
# Parent class representing a user
class User:
def __init__(self, username, email):
self.username = username
self.email = email
def show_user(self):
print(f"Username: {self.username}, Email: {self.email}")
# Creating an object of the User class
u1 = User("ananya_92", "ananya@example.com")
u1.show_user() # Output: Username: ananya_92, Email: ananya@example.com
Username: ananya_92, Email: ananya@example.com
Creating a Child Class¶
- To inherit from a class, pass the parent class as an argument to the child class.
# Customer class inherits from User
class Customer(User):
pass
c1 = Customer("rohan_k", "rohan@example.com")
c1.show_user() # Output: Username: rohan_k, Email: rohan@example.com
Username: rohan_k, Email: rohan@example.com
Customizing the Child Class with __init__¶
- So far, we've defined a child class that inherits attributes and behaviors (methods) from its parent class.
- Now, let’s enhance the child class by explicitly defining its own
__init__()method, replacing the earlier use of thepassstatement. - The
__init__()method is a special constructor in Python. It gets invoked automatically whenever an object of the class is instantiated. - However, once the
__init__()method is defined in the child class, it replaces the parent class’s constructor — meaning the child no longer inherits the parent’s__init__()method.
class Customer(User):
def __init__(self, username, email, membership):
super().__init__(username, email)
self.membership_type = membership
# Creating a Customer object
c2 = Customer("lavanya_01", "lavanya@example.com", "Gold")
c2.show_user() # Output: Username: lavanya_01, Email: lavanya@example.com
print(c2.membership_type) # Output: Gold
Username: lavanya_01, Email: lavanya@example.com Gold
Python super() Function¶
- The
super()function is used to refer to the parent class without naming it directly. It is especially useful when working with multiple inheritance.
class Organization:
def get_name(self):
return "DataEdge Solutions"
class Developer(Organization):
def show_role(self):
print(f"Working at {super().get_name()} as Developer")
d1 = Developer()
d1.show_role()
# Output: Working at DataEdge Solutions as Developer
Working at DataEdge Solutions as Developer
Adding Methods in the Child Class¶
- If the child class defines a method with the same name as one in the parent class, it will override the method inherited from the parent. In other words, the parent’s version will be replaced by the child’s implementation when called from an instance of the child class.
class Customer(User):
def __init__(self, username, email, membership):
super().__init__(username, email)
self.membership_type = membership
def greet_user(self):
print(f"Hello {self.username}, welcome to the {self.membership_type} plan!")
c3 = Customer("manav.singh", "manav@example.com", "Platinum")
c3.greet_user()
# Output: Hello manav.singh, welcome to the Platinum plan!
Hello manav.singh, welcome to the Platinum plan!
Adding Properties in the Child Class¶
When extending a parent class, you can introduce additional properties in the child class that are unique to its purpose.
In this example, we create a
Customerclass that inherits from aUserclass and includes an extra attribute:membership_type.Below, the membership_type is inside the constructor and always set to Gold.
class Customer(User):
def __init__(self, username, email):
super().__init__(username, email)
self.membership_type = "Gold"
def greet_user(self):
print(f"Hello {self.username}, welcome to the {self.membership_type} plan!")
c3 = Customer("manav.singh", "manav@example.com")
c3.greet_user()
# Output: Hello manav.singh, welcome to the Gold plan!
Hello manav.singh, welcome to the Gold plan!
- To make the class more flexible and reusable, pass the membership value to the constructor
__init__and assign it to the instance variable.
class Customer(User):
def __init__(self, username, email, membership):
super().__init__(username, email)
self.membership_type = membership
def greet_user(self):
print(f"Hello {self.username}, welcome to the {self.membership_type} plan!")
c3 = Customer("manav.singh", "manav@example.com", "Platinum")
c3.greet_user()
# Output: Hello manav.singh, welcome to the Platinum plan!
Hello manav.singh, welcome to the Platinum plan!
Checking Subclass and Instance¶
- Python provides two built-in functions for inheritance checks:
| Function | Description |
|---|---|
| issubclass(A, B) | returns True if A is a subclass of B |
| isinstance(obj, C) | Returns True if obj is an instance of C or its subclass |
class Appliance: pass
class Fridge(Appliance): pass
print(issubclass(Fridge, Appliance)) # True
print(isinstance(Fridge(), Appliance)) # True
True True
Overriding Methods¶
- In object-oriented programming (OOP), method overriding allows a child class to redefine a method that already exists in its parent class.
- This helps extend or modify behavior without changing the original parent class.
- It ensures flexibility, making code reusable and adaptable for different scenarios.
- In Python, method overriding happens when a subclass defines a method with the same name as a method in the superclass. When called, the subclass version takes priority.
How Method Overriding Works¶
- If the method exists in both parent and child classes, Python will always use the child’s version when invoked from a child instance.
- This allows us to customize functionality for specialized use cases.
1. Basic Method Overriding¶
class Device:
def start(self):
print("Starting generic device")
class Laptop(Device):
def start(self): # Overrides parent method
print("Booting laptop with OS")
l1 = Laptop()
l1.start() # Output: Booting laptop with OS
Booting laptop with OS
2. Calling the Parent Method with super()¶
- Sometimes, you may want to extend the parent’s functionality instead of replacing it entirely. This is where super() comes in.
class Model:
def evaluate(self):
return "Evaluating model with accuracy metric"
class NLPModel(Model):
def evaluate(self):
base_eval = super().evaluate() # Call parent method
return base_eval + " and F1-score for NLP tasks"
# Instance
nlp = NLPModel()
print(nlp.evaluate())
Evaluating model with accuracy metric and F1-score for NLP tasks
3. Overriding with Extra Parameters¶
- Child classes can override methods with additional parameters to make them more specific.
class DataPreprocessor:
def clean(self):
return "Cleaning raw data"
class TextPreprocessor(DataPreprocessor):
def clean(self, lowercase=True):
if lowercase:
return "Cleaning text data and converting to lowercase"
else:
return "Cleaning text data without lowercasing"
# Instance
tp = TextPreprocessor()
print(tp.clean()) # Uses default parameter
print(tp.clean(False)) # Custom parameter
Cleaning text data and converting to lowercase Cleaning text data without lowercasing
Types of Inheritance in Python¶
1. Single Inheritance¶
- A single child class inherits from one parent class.
class Engine:
def ignite(self):
print("Engine started")
class Car(Engine):
def drive(self):
print("Car is driving")
my_car = Car()
my_car.ignite() # Output: Engine started
my_car.drive() # Output: Car is driving
Engine started Car is driving
2. Multiple Inheritance¶
- A class inherits from more than one parent class.
class GPS:
def show_location(self):
print("Showing GPS coordinates")
class Camera:
def take_picture(self):
print("Capturing photo")
class SmartWatch(GPS, Camera):
pass
watch = SmartWatch()
watch.show_location() # Output: Showing GPS coordinates
watch.take_picture() # Output: Capturing photo
Showing GPS coordinates Capturing photo
3. Multilevel Inheritance¶
- A class inherits from a child class, which itself inherits from a parent class.
class Root:
def identify(self):
print("I am the base")
class Intermediate(Root):
def middle_function(self):
print("Intermediate level")
class Leaf(Intermediate):
def final_function(self):
print("Leaf node")
obj = Leaf()
obj.identify() # Output: I am the base
obj.middle_function() # Output: Intermediate level
obj.final_function() # Output: Leaf node
I am the base Intermediate level Leaf node
4. Hierarchical Inheritance¶
- Multiple child classes inherit from a single parent class.
class Platform:
def connect(self):
print("Connected to platform")
class Buyer(Platform):
def browse_items(self):
print("Browsing products")
class Seller(Platform):
def list_items(self):
print("Listing items for sale")
b = Buyer()
s = Seller()
b.connect()
b.browse_items()
s.connect()
s.list_items()
Connected to platform Browsing products Connected to platform Listing items for sale
5. Hybrid Inheritance¶
- A mix of multiple and hierarchical inheritance.
class Root:
def show_root(self):
print("Root class")
class Branch1(Root):
def show_branch1(self):
print("Branch1 class")
class Branch2(Root):
def show_branch2(self):
print("Branch2 class")
class FinalClass(Branch1, Branch2):
def show_final(self):
print("Final class")
f = FinalClass()
f.show_root()
f.show_branch1()
f.show_branch2()
f.show_final()
Root class Branch1 class Branch2 class Final class
Method Resolution Order (MRO)¶
- In Python, when multiple parent classes define the same method, the MRO determines which one is used.
Python searches left to right: Z → X → Y → object
class X:
def process(self):
print("X processing")
class Y:
def process(self):
print("Y processing")
class Z(X, Y):
pass
z_obj = Z()
z_obj.process() # Output: X processing
print(Z.mro()) # Output: [Z, X, Y, object]
X processing [<class '__main__.Z'>, <class '__main__.X'>, <class '__main__.Y'>, <class 'object'>]
Python Composition¶
Composition is a design principle in object-oriented programming that enables code reuse without inheritance.
Instead of extending a parent class, a class contains another class as one of its attributes.
This creates a “has-a” relationship (e.g., a Car has an Engine).
In contrast, inheritance represents an “is-a” relationship (e.g., a Car is a Vehicle).
Syntax of Composition
class ClassA:
# Attributes and methods of ClassA
pass
class ClassB:
def __init__(self):
self.object_a = ClassA() # Composition: ClassB "has a" ClassA instance
# Class representing the Engine
class Engine:
def start(self):
return "Engine has started."
def stop(self):
return "Engine has stopped."
# Class representing the Car, which "has an" Engine
class Car:
def __init__(self, brand):
self.brand = brand
self.engine = Engine() # Composition: Car HAS an Engine
def drive(self):
# Using the Engine inside Car
print(f"{self.brand} car is now driving.")
print(self.engine.start())
def park(self):
print(f"{self.brand} car is parked.")
print(self.engine.stop())
# Example usage
car = Car("Intensity")
car.drive()
car.park()
Intensity car is now driving. Engine has started. Intensity car is parked. Engine has stopped.
Inheritance vs. Composition¶
| Feature | Inheritance (“is-a”) | Composition (“has-a”) |
|---|---|---|
| Relationship | Subclass extends parent (e.g., Car is-a Vehicle) | One class contains another (e.g., Car has-a Engine) |
| Code Reuse | Reuse via extending parent class | Reuse via object inclusion |
| Flexibility | Less flexible (tight coupling with parent) | More flexible (loosely coupled classes) |
| Complexity | Deep hierarchies can be hard to maintain | Encourages modular and maintainable design |