This chapter concludes the OOP section by introducing two fundamental principles: Inheritance (creating hierarchies of classes) and Polymorphism (allowing different objects to respond to the same message in their own way). These concepts are key to writing flexible and modular code.
1. Inheritance
Inheritance is the mechanism that allows a new class (the Child or Subclass) to adopt the attributes and methods of an existing class (the Parent or Superclass). This promotes code reuse and establishes a logical hierarchy (an “is-a” relationship).
A. Defining a Subclass
In Python, a subclass inherits from a parent by including the parent’s name in parentheses during the class definition.
# Parent/Superclass
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name} is eating."
# Child/Subclass (inherits from Animal)
class Dog(Animal):
def bark(self):
return f"{self.name} says Woof!"
# The Dog object has both the name attribute and the eat() method
my_dog = Dog("Buddy")
print(my_dog.eat()) # Inherited method
# Output: Buddy is eating.
B. The super() Function
When a subclass defines its own constructor (__init__), it often needs to call the parent class’s constructor to properly initialize inherited attributes. The super() function allows you to call a method from the parent class directly.
class Cat(Animal):
def __init__(self, name, breed):
# Call the parent's constructor to initialize self.name
super().__init__(name)
self.breed = breed
def eat(self):
# Method Overriding: Changing the parent's behavior
return f"{self.name} is a {self.breed} and enjoys fish."
my_cat = Cat("Whiskers", "Siamese")
print(my_cat.eat())
# Output: Whiskers is a Siamese and enjoys fish.
C. Method Overriding
When a subclass defines a method with the exact same name as a method in its parent class, the subclass’s version is used. This is called Method Overriding, allowing the subclass to provide its own specific implementation of an inherited behavior (as seen with the Cat.eat() method above).
2. Polymorphism
Polymorphism (meaning “many shapes”) is the principle that allows objects of different classes to be treated as objects of a common type. More simply, it allows different objects to respond to the same method call in their own specific way.
This is a natural fit in Python because the language doesn’t strictly enforce types (Duck Typing: “If it walks like a duck and quacks like a duck, it’s a duck”).
class Circle:
def area(self, radius):
return 3.14 * radius**2
class Rectangle:
def area(self, width, height):
return width * height
# Both objects respond to the same message ("calculate area")
circle = Circle()
rectangle = Rectangle()
# Function processes different objects using the same method name
def describe_shape(shape):
if isinstance(shape, Circle):
print(f"Circle Area: {shape.area(5)}")
elif isinstance(shape, Rectangle):
print(f"Rectangle Area: {shape.area(10, 4)}")
describe_shape(circle)
describe_shape(rectangle)
# The describe_shape function works because both classes have an 'area' method.
