Difficulty: Intermediate
Inheritance allows a class (the child or subclass) to acquire the attributes and methods of another class (the parent or superclass). This promotes code reuse: you define common behavior in a base class and extend or specialize it in subclasses. In Python you specify the parent class in parentheses after the class name, e.g., class Child(Parent).
When a child class defines a method with the same name as one in the parent, it overrides that method. The child's version is called instead of the parent's when invoked on a child instance. If you still need the parent's implementation you call it explicitly using super(). The super() function returns a proxy object that delegates method calls to the parent class, following the method resolution order.
Python supports multiple inheritance, where a class can inherit from more than one parent: class C(A, B). This is powerful but introduces complexity around which parent's method is called when both parents define the same method. Python resolves this using the C3 linearization algorithm, which produces a deterministic Method Resolution Order (MRO). You can inspect the MRO with ClassName.__mro__ or ClassName.mro().
The super() function is MRO-aware. When you call super().method() inside class C, Python does not simply go to C's direct parent. It follows the MRO and calls the next class in the chain. This is essential for cooperative multiple inheritance, where each class in the hierarchy calls super() to ensure every class in the MRO gets a chance to run its version of the method. Without this cooperation, some parent __init__ methods might be skipped.
The isinstance() and issubclass() built-in functions are the idiomatic way to check inheritance relationships at runtime. isinstance(obj, ClassName) returns True if obj is an instance of ClassName or any of its subclasses. issubclass(ChildClass, ParentClass) checks whether one class is derived from another. These are preferred over direct type checks with type() because they respect the inheritance hierarchy.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f'{self.name} makes a sound'
def info(self):
return f'Animal: {self.name}'
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # call parent __init__
self.breed = breed
def speak(self): # override parent method
return f'{self.name} barks'
def info(self):
return f'Dog: {self.name}, Breed: {self.breed}'
dog = Dog('Rex', 'Labrador')
print(dog.speak())
print(dog.info())
print(isinstance(dog, Dog))
print(isinstance(dog, Animal))
Dog inherits from Animal and overrides both speak() and info(). super().__init__(name) calls Animal's __init__ so the name attribute is properly set. isinstance confirms that a Dog is also an Animal.
class A:
def greet(self):
return 'Hello from A'
class B(A):
def greet(self):
return 'Hello from B'
class C(A):
def greet(self):
return 'Hello from C'
class D(B, C):
pass
d = D()
print(d.greet())
print(D.__mro__)
D inherits from both B and C. Since D does not override greet(), Python follows the MRO: D -> B -> C -> A -> object. B is checked first and has greet(), so B's version runs. The __mro__ tuple shows the full resolution order.
class Base:
def __init__(self):
self.initialized = []
print('Base init')
class Left(Base):
def __init__(self):
super().__init__()
self.initialized.append('Left')
print('Left init')
class Right(Base):
def __init__(self):
super().__init__()
self.initialized.append('Right')
print('Right init')
class Child(Left, Right):
def __init__(self):
super().__init__()
self.initialized.append('Child')
print('Child init')
c = Child()
print(c.initialized)
Each class calls super().__init__(), and Python follows the MRO: Child -> Left -> Right -> Base. super() in Left does not call Base directly; it calls the next in MRO, which is Right. This ensures every class is initialized exactly once.
class Vehicle:
pass
class Car(Vehicle):
pass
class ElectricCar(Car):
pass
ev = ElectricCar()
print(isinstance(ev, ElectricCar))
print(isinstance(ev, Car))
print(isinstance(ev, Vehicle))
print(issubclass(ElectricCar, Car))
print(issubclass(ElectricCar, Vehicle))
print(issubclass(Car, ElectricCar))
isinstance checks the entire inheritance chain, so an ElectricCar instance is also a Car and a Vehicle. issubclass works on classes, not instances. Car is not a subclass of ElectricCar, so the last check is False.
single inheritance, multiple inheritance, super(), MRO, method overriding, isinstance