It is common to have an abstract base class that doesn’t have concrete methods/attributes but enforces contracts in children classes.
You are not meant to instantiate objects from these abstract classes directly.
Example: an abstract Shape class that has an area() method, without a concrete implementation. Every (concrete) child class of Shape, must provide a concrete implementation of the area() method.
Abstract base classes can sometimes have concrete implementations for some methods (especially if those are meant to be used as is by the child classes).
Example Abstract Base Class
import mathclass Shape:def area(self) ->float:raiseNotImplementedError('Subclasses must implement area()')def perimeter(self) ->float:raiseNotImplementedError('Subclasses must implement perimeter()')class Circle(Shape):def__init__(self, radius: float) ->None:self._radius = radiusdef area(self) ->float:return math.pi *self._radius *self._radiusdef perimeter(self) ->float:return2* math.pi *self._radiusclass Rectangle(Shape):def__init__(self, width: float, height: float) ->None:self._width = widthself._height = heightdef area(self) ->float:returnself._width *self._heightdef perimeter(self) ->float:return2* (self._width +self._height)shapes = [ Circle(3.0), Rectangle(2.5, 3.0), Rectangle(1.0, 1.0)]for shape in shapes:# Useful way to get the name of the class an object belongs to shape_name =type(shape).__name__# We don't have to check what specific kind of shape we have,# since all shapes should have area() and perimeter() which perform# the appropriate behaviour for the type of shape they are area = shape.area() perimeter = shape.perimeter()print(f'{shape_name} has area {area} and perimeter {perimeter}')
Circle has area 28.274333882308138 and perimeter 18.84955592153876
Rectangle has area 7.5 and perimeter 11.0
Rectangle has area 1.0 and perimeter 4.0
Note
Optionally, you can look into using the ABC class and abstractmethod decorator from the abc module in your own projects. For this course, however, you must not do this in your assignments.
Inheritance models
Python supports several inheritance models:
Single inheritance — a class inherits from a single parent class
Multilevel inheritance — a class inherits from a child class, which in turn inherits from another parent class. It forms a linear chain of inheritance, creating a parent -> child -> grandchild relationship.
Hierarchical inheritance — multiple child classes inherit from a single parent class
Multiple inheritance — a class inherits from multiple parent classes. This is an advanced technique.
Method Resolution Order (MRO)
Question: If a class inherits from two parents, and both parents have a method with the same name, which one does Python use?
MRO stands for Method Resolution Order — it defines the order in which Python looks through classes to find a method or attribute when it’s called on an object.
It determines which method gets called when there are multiple implementations.
Stored in cls.__mro__
Single Inheritance
Consider the following code snippet (from the course notes). Note object is the universal class.
class A(object):def__init__(self, x):self.x = xdef f(self):returnself.xdef g(self):return2*self.xdef fg(self):returnself.f() -self.g()>>> a = A(3)>>> a.x3>>> a.f()6>>> a.fg()-3
Note: even though the B class does have a grandparent (the object class), we still say it’s single inheritance, since every class in Python inherits from object eventually.
class B(A):# __init__, f, and fg are all inherited from the A class# override the g method from Adef g(self):returnself.x **2>>> b = B(7)>>> b.x7>>> b.f()7>>> b.g()49>>> b.fg()-42
Multilevel Inheritance
class C(B):# Extend the __init__ inherited from A via Bdef__init__(self, x, y):super().__init__(x)self.y = y# Inherit the f method from A via B# Inherit the g method from B# Extend the fg method inherited from A via Bdef fg(self):returnsuper().fg() *self.y>>> c = C(3, 5)>>> c.x3>>> c.y5>>> c.f()3>>> c.g()9>>> c.fg()-30
Hierarchical Inheritance
class D(A):# Inherit __init__, g, and fg from A# Override the f methoddef f(self):return-2*self.g()>>> d = D(3)>>> d.x3>>> d.f()-12>>> d.g()6>>> d.fg()-18
The UML diagram for these classes is shown below:
Multiple Inheritance
class E(B, D):pass
E inherits from both B and D.
B and D both inherit from A.
If B and D provide different implementations of a method that E inherits, which one should it use?
Python resolves this with the MRO C3 linearisation.
C3 Linearisation
Child classes are checked before parents
Parents are checked in the order they are listed in the class definition
If a class appears multiple times in the MRO, only the last occurrence is kept
>>> E.mro()[<class'__main__.E'>, <class'__main__.B'>,<class'__main__.D'>, <class'__main__.A'>, <class'object'>]>>>for cls in E.__mro__:... print(cls.__name__)EBDAobject>>> e = E(3)>>> e.x3>>> e.f()-18>>> e.g()9>>> e.fg()-27
super() function and the MRO
The super() function follows the Method Resolution Order, not just the immediate parent.
Implement some classes that inherit from the Shape class and support the given interface.
class Shape:pass# Implement Shapeclass Rectangle(Shape):pass# Implement Rectangleclass Square(Rectangle):pass# Implement Squarer = Rectangle(10, 5)s = Square(10)r < s # True
Summary
When a class inherits from multiple parents, it’s possible for more than one parent to define the same method or attribute.
To avoid confusion and ensure consistency, Python uses Method Resolution Order (MRO) to determine the order in which classes are searched.
MRO follows a well-defined path based on class hierarchy and inheritance order, ensuring that each method or attribute is found in a predictable and logical way — especially important in complex cases such as the diamond pattern.