How to Extend a Class in Python

Shikha Chaudhary Feb 12, 2024
  1. Python Extend Class Using Inheritance
  2. Use Composition to Extend a Class in Python
  3. Use Monkey Patching to Extend a Class in Python
  4. Use Decorators to Extend a Class in Python
  5. Using Mixins to Extend a Class in Python
  6. Conclusion
How to Extend a Class in Python

In object-oriented programming, class extension is a fundamental concept that allows developers to build upon existing classes, promoting code reusability and modularity. In Python, there are several methods to extend a class, each offering distinct advantages and use cases.

In Python, we can extend a class to create a new class from the existing one. This becomes possible because Python supports the feature of inheritance.

Using inheritance, we can make a child class with all the parent class’s features and methods. We can also add new features to the child class other than those present in the parent class.

We can even override those features of the parent class that we don’t need. You will learn how to do all that as you go through this article.

Python Extend Class Using Inheritance

In simple terms, extending a class means that we want to create a new class or child class from an existing or parent class. Extending a class is the same as inheriting a class.

Let us see how inheritance works in Python:

Inheritance is a classic mechanism for class extension in Python. It allows a new class, known as the derived or child class, to inherit attributes and methods from an existing class, known as the base or parent class. This facilitates code reuse and the creation of specialized classes.

To inherit a class in Python, we pass the name of that class as a parameter while creating the child class.

Syntax:

class ChildClass(ParentClass)

Let us understand this with an example:

We first create a parent class, Desserts, with two methods - init and intro. The method intro has a print statement that prints the flavor and color shown in the output.

class Desserts:
    def __init__(self, flavor, color):
        self.flavor = flavor
        self.color = color

    def intro(self):
        print(self.flavor, self.color)


obj = Desserts("Vanilla", "Pink")
obj.intro()

Output:

Vanilla Pink

We create the child class, Cake, that will inherit the class, Desserts. For the Cake class to inherit the Desserts class, we will pass the name of the Desserts class as a parameter while creating the Cake class.

class Cake(Desserts):
    pass

We are using the pass keyword here because we are only inheriting the class, Desserts, and not adding any other method or feature to the child class, Cake. The Cake class now has the same attributes and methods as the Desserts class.

We can verify this by creating an object of the Cake class and then executing the intro method, as shown in the last two lines of code.

class Desserts:
    def __init__(self, flavor, color):
        self.flavor = flavor
        self.color = color

    def intro(self):
        print(self.flavor, self.color)


obj = Desserts("Vanilla", "Pink")
obj.intro()


class Cake(Desserts):
    pass


obj = Cake("Black forest", "Black")
obj.intro()

Output:

Vanilla Pink
Black forest Black

See how the Cake class also executes the intro method like the Desserts class.

Let us see how we can add more methods and attributes to this newly created child class.

Add the __init__() Function to the Child Class After Extending a Class

We can add a new init() function to the child class even when inheriting a class. Note that whenever an object of a class is created, the init() function is automatically called.

Also, adding the init() function to the child class will not use the parent class’s init() function.

class Cake(Desserts):
    def __init__(self, flavor, color):
        self.flavor = flavor
        self.color = color

Although the init() method of the child class overrides the inheritance of the init() method of the parent class, we can still use the parent’s init() method by calling it like this:

class Cake(Desserts):
    def __init__(self, flavor, color):
        Desserts.__init__(self, flavor, color)

Use the super() Function After Extending a Class in Python

In Python, the super() function can be used to access the attributes and methods of a parent class.

When there is a new init() inside a child class that is using the parent’s init() method, then we can use the super() function to inherit all the methods and the properties from the parent class.

class Cake(Desserts):
    def __init__(self, flavor, color):
        super().__init__(flavor, color)

Note that here, we are not specifying the name of the parent class; the super() function automatically identifies it.

This was all about the basics of inheritance in Python.

But what if we have to add more attributes or methods to the child class? Let us see how we can do that below.

Add Attributes to the Child Class After Extending a Class in Python

We can add extra attributes to a child class other than those inherited by the parent class just like we add any other attribute. See how a property called quantity is added to the Cake class.

class Cake(Desserts):
    def __init__(self, flavor, color, quantity):
        super().__init__(flavor, color)
        self.quantity = quantity

We added one more parameter in the child class’s init() function. Also, do not forget to pass one more value for quantity while creating the object of the Cake class like this:

obj = Cake("Black forest", "Black", 5)

Add Methods to the Child Class After Extending a Class in Python

We add a method price in the child class by simply using the def keyword in the code below. We also pass the self keyword as the parameter.

class Cake(Desserts):
    def __init__(self, flavor, color, quantity):
        super().__init__(flavor, color)
        self.quantity = quantity

    def price(self):
        print("Pay for: ", self.quantity, "items.")

The complete code would be as follows:

class Desserts:
    def __init__(self, flavor, color):
        self.flavor = flavor
        self.color = color

    def intro(self):
        print(self.flavor, self.color)


obj = Desserts("Vanilla", "Pink")
obj.intro()


class Cake(Desserts):
    def __init__(self, flavor, color, quantity):
        super().__init__(flavor, color)
        self.quantity = quantity

    def price(self):
        print("Pay for: ", self.quantity, "items")


obj = Cake("Black forest", "Black", 5)
obj.intro()
obj.price()

Output:

Vanilla Pink
Black forest Black
Pay for:  5 items

Note that adding a method with the same name as any method in the parent class will override the inherited method. This is how we can extend a class in Python using inheritance.

Refer to this official documentation to learn more.

Use Composition to Extend a Class in Python

Composition is a design principle that involves creating a new class by combining instances of existing classes. This is achieved by including instances of other classes as attributes within the new class. Composition provides a flexible and modular approach to class extension.

Let’s dive into a detailed example to illustrate how composition can be used to extend a class in Python. Consider a scenario where we want to model a car that can have different engines.

class Engine:
    def start(self):
        return "Engine Started"


class Car:
    def __init__(self, engine):
        self.engine = engine

    def drive(self):
        return f"Car is moving. {self.engine.start()}"

In this example, we have two classes: Engine and Car. The Car class takes an instance of the Engine class as an attribute during initialization. This is a classic example of composition.

Let’s create instances and see how composition works:

# Creating an instance of the Engine class
my_engine = Engine()

# Creating an instance of the Car class with the Engine instance
my_car = Car(my_engine)

# Accessing methods through composition
print(my_car.drive())

Here, the Car class does not inherit from the Engine class, but it includes an instance of the Engine class as an attribute. The drive method of the Car class utilizes the start method of the Engine class through composition.

Output:

Car is moving. Engine Started

Handling Composition Dynamically

Composition allows for dynamic changes during runtime, making it a flexible approach. For instance, you can change the engine of a car dynamically:

# Creating a different instance of the Engine class
new_engine = Engine()

# Changing the engine of the Car instance dynamically
my_car.engine = new_engine

# Accessing methods with the new engine through composition
print(my_car.drive())

Output:

Car is moving. Engine Started

This demonstrates how composition enables dynamic changes to components, providing adaptability in different scenarios.

Use Monkey Patching to Extend a Class in Python

Monkey patching involves dynamically modifying or extending a class or module at runtime. It allows developers to alter the behavior of existing code without modifying the source.

Monkey patching is often used for quick fixes, testing, or extending functionalities of third-party libraries when the source code is not directly accessible.

While powerful, it should be used judiciously, as it can lead to unexpected behavior. Monkey patching is often employed to add or modify methods of an existing class.

Let’s consider a scenario where we have a Calculator class with an add method. We want to dynamically add a multiply method to the class using monkey patching.

class Calculator:
    def add(self, a, b):
        return a + b


# Monkey patching to add a new method


def multiply(self, a, b):
    return a * b


Calculator.multiply = multiply

# Creating an instance of the modified class
math_instance = Calculator()

# Accessing the original and patched methods
print(math_instance.add(2, 3))  # Output: 5
print(math_instance.multiply(2, 3))  # Output: 6

Output:

5
6

In this example, the multiply method is dynamically added to the Calculator class using monkey patching. This allows us to use both the original add method and the newly added multiply method on instances of the Calculator class.

Use Decorators to Extend a Class in Python

Decorators are functions that take another function or method as input and extend or modify its behavior. In Python, decorators are denoted by the @decorator syntax and are applied to functions or methods directly above their definitions.

Decorators provide a clean and reusable way to extend or modify the behavior of a class or its methods. By applying decorators to methods, developers can augment their functionality without modifying the original class.

Let’s consider a scenario where we have a TextProcessor class with a process_text method. We want to extend the class by adding a decorator that converts the output of the method to uppercase.

In this example, the uppercase_decorator is a generic decorator that takes a method as input, wraps it in a new method (wrapper), and returns the modified method. The wrapper method applies the original method and then converts its output to uppercase.

# Decorator function
def uppercase_decorator(func):
    def wrapper(self, text):
        result = func(self, text)
        return result.upper()

    return wrapper


class TextProcessor:
    @uppercase_decorator
    def process_text(self, text):
        return f"Processed: {text}"


# Creating an instance of the class with a decorated method
processor_instance = TextProcessor()

# Accessing the decorated method
result = processor_instance.process_text("Hello, World!")
print(result)  # Output: PROCESSED: HELLO, WORLD!

Output:

PROCESSED: HELLO, WORLD!

Here, the @uppercase_decorator is applied to the process_text method of the TextProcessor class, modifying its behavior without altering the original method. Then, the process_text method of the TextProcessor class is extended by the decorator, converting the output to uppercase.

Using Mixins to Extend a Class in Python

Mixins are classes that provide specific functionalities and can be combined with other classes to extend their behavior. Unlike traditional inheritance, where a class inherits from a single base class, a class can include multiple mixins, combining their features.

Mixins are intended to be small, focused, and reusable components that can be easily combined to create more complex classes.

Let’s consider a scenario where we have a User class, and we want to extend its functionality by adding a mixin for logging.

class LoggingMixin:
    def log(self, message):
        print(f"Log: {message}")


class User:
    def __init__(self, name):
        self.name = name


# Combining the User class with the LoggingMixin


class LoggedUser(LoggingMixin, User):
    pass


# Creating an instance of the combined class
logged_user = LoggedUser("John")

# Accessing methods from the base class and mixin
print(logged_user.name)  # Output: John
logged_user.log("Action taken")  # Output: Log: Action taken

Output:

John
Log: Action taken

Here, the LoggingMixin provides a log method, and the LoggedUser class combines the functionalities of both the User class and the LoggingMixin. Developers can choose to include specific mixins based on the desired functionality.

This code showcases the use of mixins to extend a class’s functionality by combining it with another class. The LoggedUser class inherits both from the User class and the LoggingMixin class, allowing instances of LoggedUser to have attributes from User and methods from LoggingMixin.

Conclusion

In conclusion, this article explored various methods of extending a class in Python, each offering distinct advantages and use cases. The discussed approaches include:

  1. Inheritance: The fundamental concept of class extension in Python allows the creation of a child class from an existing or parent class. Inheritance facilitates code reuse and the addition of new features to the child class.
  2. Composition: A design principle where a new class is created by combining instances of existing classes. This approach promotes modularity and flexibility, with instances of the new class containing attributes of the included classes.
  3. Monkey Patching: A dynamic modification or extension of a class or module at runtime. Monkey patching is useful for quick fixes, testing, or extending functionalities without directly modifying the original source code.
  4. Decorators: Functions that modify or extend the behavior of another function or method. Applied using the @decorator syntax, decorators provide a clean and reusable way to augment the functionality of a class or its methods.
  5. Mixins: Classes that provide specific functionalities and can be combined with other classes to extend their behavior. Mixins offer a modular and reusable approach, allowing developers to combine multiple mixins with a base class to create more complex classes.

Whether through inheritance, composition, monkey patching, decorators, or mixins, the diverse options empower Python developers to create modular and extensible code. Understanding these methods allows for informed design decisions and the creation of robust and scalable software.

Related Article - Python Class