Inheritance and Polymorphism
Basic Inheritance
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Some generic sound"
class Dog(Animal): # Dog inherits from Animal
def speak(self): # overrides the parent's method
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
Dog("Rex").speak() # 'Rex says Woof!'
Cat("Tom").speak() # 'Tom says Meow!'super(): Calling the Parent’s Implementation
class Animal:
def __init__(self, name, sound):
self.name = name
self.sound = sound
class Dog(Animal):
def __init__(self, name):
super().__init__(name, sound="Woof") # reuse parent's setup logic
self.tricks = []
rex = Dog("Rex")
rex.sound # 'Woof'Always call
super().__init__()in a subclassSkipping it means the parent class never gets to set up its own state, which is a common source of subtle bugs where inherited attributes are simply missing.
Polymorphism: Same Interface, Different Behavior
animals = [Dog("Rex"), Cat("Tom"), Dog("Buddy")]
for animal in animals:
print(animal.speak()) # each object responds according to its OWN classThis is the core value of inheritance: calling code does not need to know or care about the exact subclass, it just calls .speak() and trusts polymorphism to dispatch to the right implementation.
isinstance() and issubclass()
isinstance(rex, Dog) # True
isinstance(rex, Animal) # True, Dog IS-A Animal
issubclass(Dog, Animal) # True
issubclass(Animal, Dog) # FalseMultiple Inheritance
Python supports inheriting from more than one class.
class Swimmer:
def swim(self):
return "swimming"
class Flyer:
def fly(self):
return "flying"
class Duck(Swimmer, Flyer):
pass
d = Duck()
d.swim() # 'swimming'
d.fly() # 'flying'Method Resolution Order (MRO)
When multiple parent classes define the same method, Python uses the C3 linearization algorithm to decide which one wins.
class A:
def greet(self):
return "A"
class B(A):
def greet(self):
return "B"
class C(A):
def greet(self):
return "C"
class D(B, C):
pass
D().greet() # 'B', because B comes first in D's inheritance list
D.__mro__ # (D, B, C, A, object), the search order Python followsInspect the MRO when multiple inheritance gets confusing
ClassName.__mro__orClassName.mro()shows the exact left-to-right, depth-first-then-merged order Python uses to resolve attribute and method lookups. This resolves nearly every “which parent’s method actually ran” question.
Abstract Base Classes as a Safer Alternative
Deep multiple inheritance hierarchies get hard to reason about. Mixins and ABC (see Abstract-Classes-and-Interfaces) are generally cleaner patterns than deep diamond inheritance.
Mixins
A mixin is a small class designed purely to be combined with others, adding a specific piece of reusable behavior, never meant to be instantiated alone.
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)
class User(JSONSerializableMixin):
def __init__(self, name, age):
self.name = name
self.age = age
User("Bob", 25).to_json() # '{"name": "Bob", "age": 25}'Extending Built-in Types
class LoggedList(list):
def append(self, item):
print(f"Appending {item}")
super().append(item)
ll = LoggedList()
ll.append(1) # prints 'Appending 1', then behaves like a normal listChecking If a Method Was Overridden
class Base:
def method(self):
pass
class Child(Base):
def method(self):
pass
"method" in Child.__dict__ # True, Child defines its own version
"method" in Base.__dict__ # True, Base defines its own too