L — Liskov Substitution Principle (Барбара Лисков): код, рассчитанный на базовый тип, должен корректно работать и с подтипом. Это вопрос не синтаксиса, а контракта: если у подкласса те же методы, но поведение нарушает ожидания вызывающего, принцип нарушен.

Классический пример: Rectangle и Square

В математике квадрат — прямоугольник, поэтому Square(Rectangle) выглядит естественно. Но Square после set_width меняет и высоту, а контракт Rectangle («set_width не трогает высоту») нарушается.

python
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)))
text
20
16
Одни и те же вызовы: Rectangle 20, Square 16
Одни и те же вызовы: Rectangle 20, Square 16

Решение: не делайте квадрат подклассом прямоугольника. Оба — братья под общим контрактом Shape и по отдельности реализуют area().

python
class Shape:
    def area(self):
        raise NotImplementedError

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w, self.h = w, h