L is the Liskov Substitution Principle (Barbara Liskov): code written for a base type must keep working correctly with a subtype. This is about the contract, not the syntax: if a subclass has the same methods but its behaviour breaks the caller's expectations, the principle is violated.
The classic example: Rectangle and Square
In mathematics a square is a rectangle, so Square(Rectangle) looks natural. But Square also changes the height after set_width, and the Rectangle contract ("set_width leaves the height alone") is broken.
class Rectangle:
def __init__(self, w, h):
self.w, self.h = w, h
def set_width(self, w):
self.w = w
def set_height(self, h):
self.h = h
def area(self):
return self.w * self.h
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
def set_width(self, w): # a square keeps its sides equal
self.w = self.h = w
def set_height(self, h):
self.w = self.h = h
def stretch(rect):
rect.set_width(5)
rect.set_height(4)
return rect.area() # the caller expects 5 * 4
print(stretch(Rectangle(1, 1)))
print(stretch(Square(1)))20
16The fix: do not make the square a subclass of the rectangle. Both are siblings under a common Shape contract and each provides area().
class Shape:
def area(self):
raise NotImplementedError