O is the Open/Closed Principle: a module should be open for extension and closed for modification. In other words, adding behaviour should not require editing code that already works and is tested. Every edit can break old cases, while a new class leaves the old ones alone.

Editing a function on the left, adding a class on the right
Editing a function on the left, adding a class on the right

The violation: an if/elif chain

Every new kind of discount needs a new branch in discount(). As the function grows the old tests are at risk.

python
def discount(kind, price):
    if kind == "regular":
        return price
    elif kind == "student":
        return price * 0.9
    elif kind == "vip":
        return price * 0.8
    raise ValueError(f"unknown kind: {kind}")

for kind in ("regular", "student", "vip"):
    print(kind, discount(kind, 100))
try:
    discount("black-friday", 100)           # a new case: we must edit discount()
except ValueError as err:
    print(err)
text
regular 100
student 90.0
vip 80.0
unknown kind: black-friday

Way 1: polymorphism

A common base class (the contract) and a subclass per case. checkout() knows only the contract. A new BlackFriday is added and nothing is edited. It builds on inheritance and ABC.

python
class Discount:
    def apply(self, price):
        return price