6.1 Inheritance is Easy
Inheritance is a fundamental concept of Object-Oriented Programming (OOP) that allows one class (called a child or subclass) to inherit fields and methods from another class (called a parent or superclass).
This approach enables creating more generic classes and reusing code, thus enhancing code organization and maintainability.
Why do you need inheritance?
Suppose you need to write some code and decide to do it in the form of a class. Then you find out there is already a class in your project that does almost everything you need. You could simply copy this class’s code into yours and enjoy.
Or you could "sort of copy". You could declare that class as the parent of your class, and then Python will add the behavior of the parent class to your class.
Imagine you are nature and want to create a Dog. What would be faster—creating a dog from a bacterium over a billion years or domesticating a wolf in 200 thousand years?
Basic inheritance example
Let's say you have a parent class Animal
with a field name:
class Animal:
def __init__(self, name):
self.name = name
We want to create 2 child classes for it—Dog
and Cat
, and also add a method speak
to both:
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
In the example above, the child classes Dog
and Cat
inherit from Animal
and add the speak
method.
The Dog
class:
- Inherits the
name
attribute fromAnimal
. - Adds a
speak
method that returns a string specific to dogs.
The Cat
class:
- Inherits the
name
attribute fromAnimal
. - Adds a
speak
method that returns a string specific to cats.
The final version of the code looks like this:
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Output: Buddy says Woof!
print(cat.speak()) # Output: Whiskers says Meow!
In this example, Animal
is the parent class, and Dog
and Cat
are child classes. The child classes inherit the name
attribute and __init__
method from the parent class Animal
, but add speak
methods.
6.2 Under the Hood of Inheritance
If you add a parent to your class, it’s very much like you copied the parent class’s code into your class.
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name):
super().__init__(name) # Call to the parent class constructor
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def __init__(self, name):
super().__init__(name) # Call to the parent class constructor
def speak(self):
return f"{self.name} says Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Output: Buddy says Woof!
print(cat.speak()) # Output: Whiskers says Meow!
This isn’t an entirely accurate description, but if you’ve never dealt with the concept of inheritance, you can think of it this way for now. We’ll add more details later.
6.3 Inheritance Hierarchy
Very often, when designing a complex model of a large group of classes, you might encounter an entire hierarchy of inheritance.
For example, you have a class Animal
—this is the base class for all animals:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement abstract method")
We even added a speak
method, but since a generic animal doesn’t speak, this method simply throws a NotImplementedError
—this is standard practice.
Then we add intermediate classes that correspond to categories of animals: Mammal
for mammals and Bird
for birds.
class Mammal(Animal):
def __init__(self, name, fur_color):
super().__init__(name) # Call to the parent class constructor
self.fur_color = fur_color
class Bird(Animal):
def __init__(self, name, wing_span):
super().__init__(name) # Call to the parent class constructor
self.wing_span = wing_span
def fly(self):
return f"{self.name} is flying with a wingspan of {self.wing_span} meters."
And finally, only at the final stage do specific species classes appear:
class Dog(Mammal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Mammal):
def speak(self):
return f"{self.name} says Meow!"
class Parrot(Bird):
def speak(self):
return f"{self.name} says Squawk!"
This is usually the code you work with:
animals = [Dog("Buddy", "brown"), Cat("Whiskers", "white"), Parrot("Polly", 0.5)]
for animal in animals:
print(animal.speak())
print(f"{dog.name} has {dog.fur_color} fur.") # Output: Buddy has brown fur.
print(f"{cat.name} has {cat.fur_color} fur.") # Output: Whiskers has white fur.
print(parrot.fly()) # Output: Polly is flying with a wingspan of 0.5 meters.
Although technically there are no restrictions on creating hierarchies with dozens of ancestors, it’s important to remember that, unless necessary, simplicity is best. Simplicity is power.
GO TO FULL VERSION