Python - Inheritance
Abstract classes and methods in Python provide a way to define a blueprint for a class without providing a complete implementation. Abstract classes cannot be instantiated, and abstract methods must be implemented by any concrete (non-abstract) subclass. They serve as a way to enforce a certain structure in the inheritance hierarchy and ensure that specific methods are implemented in the derived classes.
Let's consider an example to illustrate the purpose of abstract classes and methods in Python inheritance:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius**2
def perimeter(self):
return 2 * 3.14 * self.radius
class Square(Shape):
def __init__(self, side_length):
self.side_length = side_length
def area(self):
return self.side_length**2
def perimeter(self):
return 4 * self.side_length
# Creating instances of the subclasses
circle_instance = Circle(5)
square_instance = Square(4)
# Accessing abstract methods
circle_area = circle_instance.area()
circle_perimeter = circle_instance.perimeter()
square_area = square_instance.area()
square_perimeter = square_instance.perimeter()
print(circle_area) # Output: 78.5
print(circle_perimeter) # Output: 31.400000000000002
print(square_area) # Output: 16
print(square_perimeter) # Output: 16
In this example, the 'Shape' class is an abstract class with abstract methods 'area' and 'perimeter'. The 'Circle' and 'Square' classes inherit from the 'Shape' abstract class and provide concrete implementations for the abstract methods. Attempting to instantiate an object of the abstract class or a subclass without implementing the abstract methods would result in an error.
The program demonstrates how abstract classes and methods enforce a specific structure in the inheritance hierarchy and ensure that derived classes implement the necessary methods.
Output:
78.5 31.400000000000002 16 16
In Python, method overloading is achieved by defining multiple methods with the same name in a class, but with different parameters. Since Python supports dynamic typing, the method to be called is determined at runtime based on the actual arguments passed during the function call.
class MathOperations:
def calculate(self, *args):
if len(args) == 2:
return self.add(args[0], args[1])
elif len(args) == 3:
return self.multiply(args[0], args[1], args[2])
else:
return "Invalid number of arguments"
def add(self, a, b):
return a + b
def multiply(self, a, b, c):
return a * b * c
# Creating an instance of the class
math_instance = MathOperations()
# Calling the overloaded methods
result_add = math_instance.calculate(5, 3)
result_multiply = math_instance.calculate(2, 4, 3)
print(result_add) # Output: 8
print(result_multiply) # Output: 24
In this example, the MathOperations
class has a method named calculate
that acts as a dispatcher. Depending on the number of arguments passed, it calls the appropriate overloaded method add
or multiply
.
The program demonstrates how method overloading can be achieved in Python by defining a single method that handles multiple cases based on the number of arguments received.
Output:
8 24
In Python, the is
and ==
operators are used to compare objects, but they have different purposes and behaviors.
The ==
operator compares the values of two objects to check if they are equal. It is used for value equality. On the other hand, the is
operator checks if two objects refer to the same memory location, essentially testing for object identity.
Let's consider an example to illustrate the use of the is
and ==
operators with objects in Python:
# Example class
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# Creating instances of the class
point1 = Point(1, 2)
point2 = Point(1, 2)
point3 = point1
# Using the == operator for value equality
result_equal = point1 == point2
# Using the is operator for object identity
result_identical = point1 is point2
result_same_reference = point1 is point3
# Displaying the results
print(result_equal) # Output: True
print(result_identical) # Output: False
print(result_same_reference) # Output: True
In this example, two instances of the Point
class (point1
and point2
) are created with the same values. The ==
operator compares their values, resulting in True
. However, the is
operator checks if they refer to the same memory location, and since they are separate instances, it returns False
.
The program demonstrates the difference between the is
and ==
operators in Python when used with objects.
Output:
True False True
In Python, preventing a method from being overridden can be achieved using external libraries like final-class
. This library introduces the @final
decorator, which can be used to mark a method as final, indicating that it should not be overridden in any subclass.
Let's consider an example using the final-class
library:
from final_class import final
# Define a class with a final method
@final
class BaseClass:
def final_method(self):
return "This method is final and cannot be overridden"
# Attempt to create a subclass and override the final method
class SubClass(BaseClass):
def final_method(self):
return "This method attempts to override the final method"
# Create an instance of the subclass
subclass_instance = SubClass()
# Access the final method
output = subclass_instance.final_method()
print(output) # Output: TypeError: Cannot override final method 'final_method'
In this example, the @final
decorator from the final-class
library is used to mark the final_method
in the BaseClass
as final. When attempting to create a subclass (SubClass
) and override the final method, a TypeError
is raised, indicating that the final method cannot be overridden.
The program demonstrates how to use external libraries like final-class
to prevent a method from being overridden in a subclass.
Output:
TypeError: Cannot override final method 'final_method'