we help you to understand Python better

This article explains the Liskov Substitution Principle with the help of a compact Python example that violates the principle.

Liskov Substitution Example with Python

The Liskov Substitution Principle states:

If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering the correctness of the program.

In this article you see a small code example in Python that violates this principle and learn why that is happening. Let’s start with valid code that has:

class Calculator():
    def calculate(self, a, b): # returns a number
        return a * b

calculation_results = [
    Calculator().calculate(3, 4),
    Calculator().calculate(5, 7),
]

print(calculation_results)

Output:

[12, 35]

Important: In the code above, the calculate function always returns a number (the product of a and b).

Break the Liskov Substitution Principle

Every subclass of Calculator needs to implement a calculation function that returns a number. Let’s break the principle by creating a calculate function that can also raise an error. This example adds a DividerCalculator class (inherits from Calculator) where the overridden calculate function raises an error when Python tries to divide by zero.

class Calculator():
    def calculate(self, a, b): # returns a number
        return a * b

class DividerCalculator(Calculator):
    def calculate(self, a, b): # returns a number or raises an Error
        return a / b           

calculation_results = [
    Calculator().calculate(3, 4),
    Calculator().calculate(5, 7),
    DividerCalculator().calculate(3, 4),
    DividerCalculator().calculate(5, 0) # 0 will cause an Error
]

print(calculation_results)

Output:

ZeroDivisionError: division by zero

There is no way to fix this code without:

  1. Refactoring the class hierarchy, or
  2. Wrap every calculation call in try/except code

What we learn here is that the DividerCalculator class is different from the Calculator class in this way:

That makes the result type different and therefore the interface different. Multiply and Divide are not the same thing when it comes to the result type and one should not derive from the other.

Suspicious code

How can you spot suspicious code that might break the Liskov Substitution Principle? Apart from the code above, here are more bad examples:

class Line(Shape):
    def calculate_surface_area(self):
        return -1 # a line does not have a surface area
class Manager(Employee):
    def desk_id(self):
        return "" # managers usually occupy meeting rooms
class CompletedTask(Task):
    def complete(self):
        raise Exception("Cannot complete a completed task")

Solve the problem

The problem is usually caused by inheriting class S from class T where S and T seem related but have one or more fundamental interface differences. You can solve this with hacks like the suspicious code above or increase or decrease the levels of abstraction in your class hierarchy.

This might lead to more code and that is fine. When your team mates start complaining about this, you can now explain to them why you took the decision. Writing correct code is more important than writing compact code.

Revisit old code and look for inherited classes with overridden functions that have confusing return values like -1, empty strings, 9999 or functions that raise exceptions. Chances are that you found a violation of the Liskov Substitution Principle.

Dive deeper into this topic with training?
By using this site, you acknowledge that you have read and understand our Cookie and Privacy Policy.