Abstraction and polymorphism

Part of the free LogicWiz Python course, Module 9: Object-oriented programming. The examples and practice run in the app.

Canteen Counter takes cash, UPI and card, and card payments cost a 2% fee. Meher wants one line, method.fee(amount), that works for all three, without a single if kind == "upi".

You'll learn to

  • Use polymorphism and duck typing: the same call, different behaviour
  • Force subclasses to implement methods with `abc.ABC` and `@abstractmethod`
  • Explain why Python has no method overloading, and what to do instead
  • Overload operators with `__add__`, `__len__`, `__contains__`, `__getitem__`

Learn

Polymorphism: one call, many forms

class Cash:
    def fee(self, amount):
        return 0

class UPI:
    def fee(self, amount):
        return 0

class Card:
    def fee(self, amount):
        return round(amount * 0.02, 2)

for method in [Cash(), UPI(), Card()]:
    print(type(method).__name__, method.fee(250))

The loop never asks what kind of payment it has. Each object answers fee in its own way. Adding a new payment type means adding a class, not editing the loop.

Duck typing

"If it walks like a duck and quacks like a duck, it's a duck." Python doesn't check an object's type before calling a method; it only checks that the method exists when the call happens. The three payment classes above don't share a parent at all, and the loop still works.

That's why len() works on lists, strings, dicts, and your class, as soon as it has __len__.

Abstraction: a contract with abc

from abc import ABC, abstractmethod

class Payment(ABC):
    @abstractmethod
    def fee(self, amount):
        ...

    def total(self, amount):              # a normal, shared method
        return amount + self.fee(amount)

class Card(Payment):
    def fee(self, amount):
        return round(amount * 0.02, 2)

print(Card().total(250))
try:
    Payment()
except TypeError as e:
    print("TypeError:", e)

An abstract base class says what every payment must do (fee) without saying how. You cannot make a Payment itself, and a subclass that forgets fee cannot be created either.

No method overloading in Python

In Java you can write area(r) and area(l, w) side by side, and the compiler picks one by the number of arguments. Python can't. A class body is like a dict of names, so the second def area simply replaces the first.

Get the same effect with default arguments (def area(self, a, b=None)), with *args, or with two clearly named methods.

The last def wins

class Calc:
    def area(self, r):
        return 3.14 * r * r

    def area(self, l, w):          # replaces the first one!
        return l * w

c = Calc()
print(c.area(3, 4))
try:
    c.area(2)
except TypeError as e:
    print("TypeError:", e)

class Calc2:
    def area(self, a, b=None):     # one method, an optional argument
        return 3.14 * a * a if b is None else a * b

print(Calc2().area(2), Calc2().area(3, 4))

Operator overloading

class Cart:
    def __init__(self, items=None):
        self.items = list(items or [])

    def __len__(self):
        return len(self.items)

    def __contains__(self, name):
        return name in self.items

    def __add__(self, other):
        return Cart(self.items + other.items)

    def __getitem__(self, i):
        return self.items[i]

a = Cart(["Chai", "Samosa"])
b = Cart(["Dosa"])
both = a + b                # calls a.__add__(b)
print(len(both), "Dosa" in both, both[0])
for thing in both:          # __getitem__ makes it loopable too
    print(thing)

+ calls __add__, len() calls __len__, in calls __contains__, [ ] calls __getitem__. These "dunder" (double-underscore) methods are how built-in syntax talks to your objects.

Overriding vs overloading

OverridingOverloading
Same method name in a child classSame method name, different parameters, in the same class
Fully supported in PythonNot supported: the last def wins
Chosen at run time by the object's classImitate with defaults, args, or functools.singledispatch
Drink.label replaces Item.labelarea(r) vs area(l, w)

"Does Python support method overloading?" is a favourite trick question in interviews and vivas. Say no, show that the second def replaces the first, and show the default-argument fix. Then mention that operator overloading is supported, through dunder methods.

In one line

Polymorphism and duck typing: same call, each object answers its own way; ABCs force subclasses to implement methods; no overloading (the last def wins), but operators overload through dunders.

Interview corner

Does Python support method overloading?

No. Defining a method twice in a class simply rebinds the name, so the last definition wins. Use default arguments, *args/**kwargs, or functools.singledispatchmethod for type-based dispatch. Python does support method overriding and operator overloading.

What is duck typing?

Python decides whether an operation works by checking at run time whether the object has the needed method, not what its declared type is. Anything with __len__ works with len(); anything with read() can act as a file. It makes code flexible, but errors show up at run time, so tests matter.

What is an abstract class? How is it made in Python?

A class that defines an interface but cannot be instantiated. In Python, subclass abc.ABC and mark methods with @abstractmethod. A subclass can be instantiated only after it implements every abstract method; otherwise you get TypeError. Abstract classes can still have normal methods with shared code.

What are magic (dunder) methods? Give examples.

Special methods with double underscores that Python calls for built-in syntax: __init__ for construction, __str__/__repr__ for printing, __add__ for +, __eq__/__lt__ for comparisons, __len__ for len(), __contains__ for in, __getitem__ for [], and __call__ to make an object callable. Defining them is operator overloading.