Inheritance is a powerful feature in object-oriented programming that allows a subclass to inherit attributes and methods from a superclass. Inheritance allows for code reuse and modularity, and it is a key concept in many programming languages, including Python.
In this article, we will introduce the basics of inheritance and explore how to use it in Python. We will cover topics such as defining superclasses and subclasses, method overriding, method resolution order (MRO), polymorphism, and abstract base classes (ABCs).
Introduction to inheritance
Inheritance is a mechanism that allows a new class to be created based on an existing class. The new class is called the “subclass,” and the existing class is the “superclass.” The subclass can have additional attributes and methods, but it can also use all the attributes and methods of the superclass. This allows for code reuse and modularity.
There are several types of inheritance:
- Single inheritance: A subclass inherits from a single superclass.
- Multiple inheritance: A subclass inherits from multiple superclasses.
- Multilevel inheritance: A subclass inherits from a superclass, which itself inherits from another superclass.
- Hierarchical inheritance: Multiple subclasses inherit from a single superclass.
Inheritance is a useful tool for creating relationships between classes, such as a “is-a” relationship (e.g., a car is a vehicle). However, it is important to use inheritance wisely and avoid creating overly complex class hierarchies.
Defining a superclass and subclass in Python
In Python, you can define a class using the class
keyword, followed by the class name and a colon. The class body is indented, and you can define attributes and methods within the class. Here is an example of a simple superclass called Shape
:
class Shape:
def __init__(self, sides):
self.sides = sides
def area(self):
pass # method to be implemented in subclass
To create a subclass, you can use the same class
keyword and specify the superclass in parentheses. The subclass will automatically inherit all the attributes and methods of the superclass. Here is an example of a subclass called Rectangle
that inherits from the Shape
superclass:
class Rectangle(Shape):
def __init__(self, width, height):
super().__init__(4) # call the superclass __init__ method
self.width = width
self.height = height
def area(self):
return self.width * self.height
In the Rectangle
subclass, we use the super()
function to call the __init__
method of the Shape
superclass. This allows us to set the sides
attribute of the Shape
class, as well as the width
and height
attributes of the Rectangle
class.
Overriding methods
Sometimes, you may want to define a subclass method that has the same name as a superclass method, but with different behavior. This is called “method overriding.” To override a method in a subclass, you can simply define a method with the same name in the subclass. The subclass method will take precedence over the superclass method. Here is an example of method overriding in Python:
class Shape:
def __init__(self, sides):
self.sides = sides
def area(self):
pass # method to be implemented in subclass
class Rectangle(Shape):
def __init__(self, width, height):
super().__init__(4)
self.width = width
self.height = height
def area(self): # override the area method
return self.width * self.height
class Triangle(Shape):
def __init__(self, base, height):
super().__init__(3)
self.base = base
self.height = height
def area(self): # override the area method
return 0.5 * self.base * self.height
In this example, both the Rectangle
and Triangle
classes override the area
method of the Shape
superclass. The Rectangle
class calculates the area using the width and height attributes, while the Triangle
class calculates the area using the base and height attributes.
If you want to call the superclass method from within the subclass method, you can use the super()
function. For example:
class Shape:
def __init__(self, sides):
self.sides = sides
def area(self):
return 0
class Rectangle(Shape):
def __init__(self, width, height):
super().__init__(4)
self.width = width
self.height = height
def area(self): # override the area method
return super().area() + self.width * self.height # call the superclass area method
In this example, the Rectangle
class calls the area
method of the Shape
superclass using the super()
function, and then adds the area calculated from the width and height attributes.
Method resolution order (MRO)
In Python, the order in which superclasses are searched for method resolution is called the “method resolution order” (MRO). The MRO is determined by the class hierarchy and the order in which superclasses are listed in parentheses. You can view the MRO of a class using the __mro__
attribute. For example:
print(Rectangle.__mro__) # (Rectangle, Shape, object)
print(Triangle.__mro__) # (Triangle, Shape, object)
You can also use the mro()
method of a class to view the MRO. For example:
print(Rectangle.mro()) # [Rectangle, Shape, object]
print(Triangle.mro()) # [Triangle, Shape, object]
It is important to understand the MRO when working with inheritance, as it can affect the behavior of method resolution.
Polymorphism and method overloading
Polymorphism is the ability for a subclass to override or extend the methods of the superclass. In Python, you can use polymorphism by defining methods with the same name in different classes, and then using them in a generic way.
Method overloading is a form of polymorphism where you define multiple methods with the same name, but with different arguments. Python does not support method overloading directly, but you can achieve a similar effect by using default arguments. Here is an example of polymorphism and method overloading in Python:
class Shape:
def __init__(self, sides):
self.sides = sides
def area(self):
pass # method to be implemented in subclass
class Rectangle(Shape):
def __init__(self, width, height):
super().__init__(4)
self.width = width
self.height = height
def area(self): # override the area method
return self.width * self.height
class Triangle(Shape):
def __init__(self, base, height):
super().__init__(3)
self.base = base
self.height = height
def area(self): # override the area method
return 0.5 * self.base * self.height
def get_area(shape, *args):
return shape.area(*args)
rect = Rectangle(10, 5)
print(get_area(rect)) # 50
tri = Triangle(10, 5)
print(get_area(tri)) # 25
In this example, the get_area
function is a generic function that accepts a Shape
object and calculates the area using the area
method of the object. The Rectangle
and Triangle
classes both override the area
method with their own implementation, so the get_area
function can be used with either a Rectangle
or Triangle
object. We can also achieve a similar effect to method overloading by using default arguments in the area
method. For example:
class Shape:
def __init__(self, sides):
self.sides = sides
def area(self, base=None, height=None):
if base is not None and height is not None:
return 0.5 * base * height
else:
pass # method to be implemented in subclass
class Rectangle(Shape):
def __init__(self, width, height):
super().__init__(4)
self.width = width
self.height = height
def area(self): # override the area method
return super().area(self.width, self.height)
class Triangle(Shape):
def __init__(self, base, height):
super().__init__(3)
self.base = base
self.height = height
def area(self): # override the area method
return super().area(self.base, self.height)
rect = Rectangle(10, 5)
print(rect.area()) # 50
tri = Triangle(10, 5)
print(tri.area()) # 25
In this example, the area
method of the Shape
class has default arguments for base
and height
. The Rectangle
and Triangle
classes override the area
method and call the superclass method with their own base
and height
values. This allows for a similar effect to method overloading, where the area
method can be called with different arguments depending on the subclass.
Abstract base classes (ABCs)
Abstract base classes (ABCs) are classes that define methods that must be implemented by their concrete subclasses. ABCs are useful for defining interfaces or contracts that must be followed by concrete classes. In Python, you can use the ABC
module and the abstractmethod
decorator to define ABCs. Here is an example:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self): # implement the abstract area method
return self.width * self.height
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def area(self): # implement the abstract area method
return 0.5 * self.base * self.height
rect = Rectangle(10, 5)
print(rect.area()) # 50
tri = Triangle(10, 5)
print(tri.area()) # 25
# Cannot create an instance of the abstract Shape class
# shape = Shape() # TypeError: Can't instantiate abstract class Shape with abstract methods area
In this example, the Shape
class is defined as an ABC using the ABC
module and the abstractmethod
decorator. The area
method is defined as an abstract method, which means it must be implemented by any concrete subclasses of the Shape
class.
The Rectangle
and Triangle
classes are concrete subclasses of the Shape
class, so they must implement the area
method. They do this by defining the area
method and providing their own implementation. You cannot create an instance of an ABC, only concrete subclasses. If you try to instantiate an ABC, you will get a TypeError
stating that you cannot instantiate an abstract class.
Conclusion
Inheritance is a powerful tool in object-oriented programming that allows for code reuse and modularity. It is based on the concepts of encapsulation, inheritance, and polymorphism, which allow for the creation of relationships between classes and the ability to override or extend methods. In Python, you can define superclasses and subclasses using the class
keyword and the super()
function, and you can use the __mro__
attribute and the mro()
method to view the method resolution order. You can also use polymorphism and default arguments to achieve a similar effect to method overloading, and you can use ABCs and the ABC
and abstractmethod
modules to define interfaces or contracts for concrete classes.