Inheritance
Inheritance lets a class reuse and extend another class. The new class (the child) gets every attribute and method of the existing class (the parent) and can add or replace what it needs.
A parent and a child
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Dog(Animal):
def speak(self):
return "Woof"
class Cat(Animal):
def speak(self):
return "Meow"
pets = [Dog("Rex"), Cat("Tom"), Animal("Generic")]
for p in pets:
print(p.name, p.speak())class Dog(Animal) means Dog inherits from Animal. Dog did not define __init__, so Animal's is used. Dog did define speak, so it overrides the parent's version.
Calling the parent with super()
When you override a method but still want the parent's behaviour, call it with super().
class Person:
def __init__(self, name):
self.name = name
def describe(self):
return f"{self.name}"
class Student(Person):
def __init__(self, name, college):
super().__init__(name)
self.college = college
def describe(self):
return super().describe() + f", studying at {self.college}"
s = Student("Kiran", "IIT Jodhpur")
print(s.describe())Forgetting super().__init__() is the most common inheritance bug: the parent's attributes never get set.
isinstance and issubclass
class Animal: pass
class Dog(Animal): pass
d = Dog()
print(isinstance(d, Dog))
print(isinstance(d, Animal))
print(issubclass(Dog, Animal))An instance of a child counts as an instance of the parent.
Method resolution order
When you call a method, Python looks in the object's class first, then its parent, then the grandparent, and so on.
class A:
def who(self): return "A"
class B(A):
pass
class C(B):
pass
print(C().who())
print([cls.__name__ for cls in C.__mro__])Multiple inheritance
A class can have several parents. Python searches them left to right. It works, but keep it simple; mixins with small, focused behaviour are the usual reason to use it.
class Walker:
def move(self): return "walking"
class Swimmer:
def swim(self): return "swimming"
class Duck(Walker, Swimmer):
pass
d = Duck()
print(d.move(), d.swim())Abstract base classes
If a parent method only makes sense when overridden, mark it abstract. Python then refuses to create instances of classes that forget to implement it.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
...
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
print(Square(4).area())
try:
Shape()
except TypeError as e:
print("Error:", e)Composition over inheritance
Inheritance says "a Dog is an Animal". If the relationship is really "a Car has an Engine", store the engine as an attribute instead of inheriting from it. Deep inheritance trees are harder to change than objects that hold other objects.
Practice
- Create a
Vehicleclass withwheelsand adescribe()method. MakeCarandBikesubclasses that set the right number of wheels. - Add an
ElectricCarthat extendsCarwith abattery_kwhattribute and overridesdescribe()usingsuper(). - Make an abstract
Employeeclass with an abstractpay()method and two concrete subclasses.
