Inheritance and polymorphism are foundational concepts of object-oriented programming (OOP) that Python supports with its versatile syntax.
They allow for the creation of a hierarchical classification of classes, enabling more complex and nuanced object relationships and behaviors.
Inheritance
Inheritance allows a class (known as a child or subclass) to inherit attributes and methods from another class (known as a parent or superclass). This mechanism promotes code reuse and a hierarchical organization of classes.
Basic Inheritance
class Animal:def__init__(self, name):self.name = namedef speak(self):raiseNotImplementedError("Subclass must implement abstract method")def begets_offspring(self) ->bool:returnTrueclass Dog(Animal):def speak(self):returnf"{self.name} says Woof!"class Cat(Animal):def speak(self):returnf"{self.name} says Meow!"class Liger(Animal):def speak(self):returnf"{self.name} says Roar!"def begets_offspring(self):returnFalseclass Pet(Animal):def__init__(self, name, owner):super().__init__(name)self.owner = ownerdef speak(self):returnf"{self.name} probably says Woof or Meow"animals = [Dog("Scooby"), Cat("Snowball"), Liger("Half Tiger Half Lion")]print([f"{animal.name}{animal.speak()}"for animal in animals])print([f"{animal.name} is sterile"for animal in animals ifnot animal.begets_offspring()])
We don’t need to define again .__init__() in the subclasses when no attributes are added to those already defined in the superclass. By leaving out the constructor definition, the subclass inherits the constructor definition from the superclass. To include new attributes (as owner in Pet), we need to redefine .__init__() and explicitly call the superclass constructor using super().__init__.
The definition of Animal.speak() in the example forces the subclasses to define an overriding method, otherwise an error will be raised.
---------------------------------------------------------------------------NotImplementedError Traceback (most recent call last)
Cell In[2], line 2 1 misterious_animal = Animal("X")
----> 2misterious_animal.speak()
Cell In[1], line 6, in Animal.speak(self) 5defspeak(self):
----> 6raiseNotImplementedError("Subclass must implement abstract method")
NotImplementedError: Subclass must implement abstract method
On the other hand, .begets_offspring(), is rather an “optional” method for subclasses of Animal and will take the default value defined in the superclass if lacking.
When using super() and adding new attributes in the subclass, we can also set them a default value in the constructor definition as show in the example for the attribute .can_fly. This way we can instantiate the subclass the same way we would with the superclass, in our case, only by assignign name to an Animal:
Via multiple inheritance, a class is allowed to inherit from more than one parent class.
class Swimming:def swim(self):return"Swims forward"class TerrestrialAnimal(Animal):def walk(self):return"walks on land"class AquaticAnimal(Animal, Swimming):def swim(self):return"swims in water"class Fish(AquaticAnimal):def speak(self):return"says ... (*mute*)"class Horse(TerrestrialAnimal):def speak(self):return"*neighs in high pitch*"def walk(self):return"trots on land"def rears_up(self):return"rears up on 2 legs"
goldfish = Fish("Dory")print(f"{goldfish.name}{goldfish.speak()} and {goldfish.swim()}")
Dory says ... (*mute*) and swims in water
bthunder_horse = Horse("Black Thunder")print(f"{bthunder_horse.name}{bthunder_horse.speak()} and {bthunder_horse.rears_up()}")
Black Thunder *neighs in high pitch* and rears up on 2 legs
Notice how TerrestrialAnimal and AcquaticAnimal do not implement a .speak() method, hence it can’t be used to instantiate an object directly. This way, we can enforce a certain intermediate inheritance to exist without giving it “permission” to make objects directly.
Polymorphism
Polymorphism allows objects of different classes to be treated as objects of a common superclass. In practice, it also allows methods to be defined in a way that they can operate on object of difference classes. This shows usefulness when they share a method name but implement it differently. The net result of this is, two classes can be in certain contextes be used interchageably.
Polymorphism can be achieved through inheritance, albeit it doesn’t necessarily need it.
Polymorphism with Inheritance
animals = [Dog("Otto"), Cat("Stella"), Fish("Nemo"), Horse("BlackThunder")]for animal in animals:print(f"{animal.name}: {animal.speak()}")ifisinstance(animal, Swimming):print(f"{animal.name} can also swim!")
Otto: Otto says Woof!
Stella: Stella says Meow!
Nemo: says ... (*mute*)
Nemo can also swim!
BlackThunder: *neighs in high pitch*
Each of the classes in the animals list has its own implementation of the speak method, which allows them to produce different outputs when the method is called. All the classes of the object at hand implement speak differently and yet we can call the different implementation seamlessly using the single method name speak.
Polymorphism without Inheritance
Python also supports duck typing, which allows for polymorphism without a formal inheritance hierarchy.
class Robot:def speak(self):return"Beep boop"def animal_sound(animal):print(animal.speak())# Duck typing animal_sound(Dog("RoboDog"))animal_sound(Robot())
RoboDog says Woof!
Beep boop
animal_sound() is called with instances of both Dog and Robot, showing that both can be used interchangeably because they both implement the speak method, without requiring a formal hierarchy.
Duck typing is a concept that emphasizes an object’s behavior over its actual type. The name is derived from the saying “If it looks like a duck and quacks like a duck, it must be a duck.”. This principle allows for polymorphism without the need for a formal inheritance structure.
In duck typing, the focus is on what an object can do, rather than what an object is. This means that if an object implements a certain method or behavior, it can be used in any context that expects that method or behavior, regardless of the object’s class.