Frameworks and exception hierarchies are built on inheritance; good design knows its limits.
class Dog(Animal): makes Dog a subclass of Animal. It inherits every attribute and method, and can override any of them by defining a method with the same name. Attribute lookup checks the instance, then the class, then its parents.
To extend rather than replace parent behaviour, call super(): super().__init__(name) runs the parent's initialiser on this object. Use super() instead of naming the parent class directly; it follows the method resolution order (MRO), which matters when classes use multiple inheritance. You can inspect it with Dog.__mro__ or Dog.mro().
isinstance(obj, Animal) is true for instances of Animal and of any subclass, and issubclass(Dog, Animal) checks the classes. Every class ultimately inherits from object.
Python supports multiple inheritance (class C(A, B)), typically used for small mixins that add one capability, like JsonMixin providing to_json(). Keep mixins narrow and always call super() so that cooperative chains work.
To declare an interface that subclasses must implement, inherit from abc.ABC and mark methods with @abstractmethod; such a class can't be instantiated until every abstract method is overridden.
Inheritance means "is-a" and couples the subclass tightly to the parent's internals. When you just want to reuse functionality, composition — holding another object as an attribute and delegating to it — is usually more flexible ("has-a"). A Car has an Engine; it isn't one. Deep inheritance hierarchies are a code smell in Python.
Example
from abc import ABC, abstractmethod
class Shape(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def area(self): ...
def describe(self):
return f"{self.name} with area {self.area():.1f}"
class Rectangle(Shape):
def __init__(self, w, h):
super().__init__("rectangle")
self.w, self.h = w, h
def area(self):
return self.w * self.h
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
self.name = "square"
sq = Square(3)
print(sq.describe()) # square with area 9.0
print(isinstance(sq, Shape)) # True
print([c.__name__ for c in Square.__mro__])
# ['Square', 'Rectangle', 'Shape', 'ABC', 'object']
# Shape("x") -> TypeError: can't instantiate abstract classRemember
- Subclasses inherit and can override;
super()calls the next class in the MRO abc.ABC+@abstractmethoddefine interfaces that must be implemented- Prefer composition (has-a) when you only need to reuse behaviour
Watch out for
- Forgetting to call
super().__init__()so parent attributes are never set.