Python Abstract Classes & Dunder Methods
An Abstract Base Class (ABC) is a blueprint class that defines a common interface contract for a set of subclasses. It cannot be instantiated directly.
Using Python's built-in abc module (ABC and @abstractmethod):
- Any subclass inheriting from an ABC MUST implement all declared abstract methods.
- If a subclass forgets to implement an abstract method, Python prevents instantiation at runtime with a
TypeError: Can't instantiate abstract class ... with abstract method.
from abc import ABC, abstractmethod
class NotificationService(ABC):
"""Abstract Base Class defining the notification contract."""
@abstractmethod
def send(self, recipient, message):
"""Must be implemented by all notification channels!"""
pass
class EmailNotification(NotificationService):
def send(self, recipient, message):
return f"๐ง Sending Email to [{recipient}]: '{message}'"
class SMSNotification(NotificationService):
def send(self, recipient, message):
return f"๐ฑ Sending SMS to [{recipient}]: '{message}'"
# 1. Attempting to instantiate the Abstract Base Class fails:
try:
service = NotificationService()
except TypeError as err:
print("๐ซ ABC Instantiation Blocked:", err)
# 2. Concrete subclasses work properly:
services = [EmailNotification(), SMSNotification()]
for s in services:
print(s.send("balaji@test.com", "Your OTP is 492810"))
ABCs guarantee that team members never deploy a half-implemented plugin or driver that crashes in production due to a missing method.
In Python, Dunder (Magic) methods are special methods surrounded by double underscores (__method__) that hook directly into Python's syntax operators:
__str__(self): Returns a user-friendly, human-readable string representation (called byprint(obj)andstr(obj)).__repr__(self): Returns an unambiguous, developer-focused representation showing exact type and parameters, ideally valid Python code to recreate the object (called by interactive REPLs and debugger inspection).
class Product:
def __init__(self, name, price):
self.name = name
self.price = float(price)
# Human-friendly display string:
def __str__(self):
return f"{self.name} (โน{self.price:,.2f})"
# Unambiguous developer representation:
def __repr__(self):
return f"Product(name={self.name!r}, price={self.price})"
item = Product("Mechanical Keyboard", 2499.00)
print("str(item) for Users: ", str(item)) # Mechanical Keyboard (โน2,499.00)
print("repr(item) for Developers:", repr(item)) # Product(name='Mechanical Keyboard', price=2499.0)
If you only implement one of them, always implement __repr__, because Python will automatically fall back to __repr__ if __str__ is missing.
Python allows your custom classes to integrate seamlessly with standard language operators:
__len__(): Hooks intolen(obj).__eq__(): Hooks into==value comparison.__add__(): Hooks into the+addition operator.
class Money:
def __init__(self, amount, currency="INR"):
self.amount = float(amount)
self.currency = currency
def __repr__(self):
return f"Money({self.amount}, '{self.currency}')"
def __str__(self):
return f"โน{self.amount:,.2f} {self.currency}"
# Overload == operator:
def __eq__(self, other):
if isinstance(other, Money):
return self.amount == other.amount and self.currency == other.currency
return False
# Overload + operator:
def __add__(self, other):
if not isinstance(other, Money) or self.currency != other.currency:
raise TypeError("Cannot add money of different currencies!")
return Money(self.amount + other.amount, self.currency)
# Test operator overloading:
m1 = Money(1500)
m2 = Money(2500)
m3 = Money(1500)
print("m1 + m2 =", m1 + m2) # Calls __add__ -> โน4,000.00 INR
print("m1 == m3?", m1 == m3) # Calls __eq__ -> True
print("m1 == m2?", m1 == m2) # Calls __eq__ -> False
With __add__ and __eq__, your custom objects behave like built-in Python primitives with natural mathematical syntax!
The __str__() and __repr__() methods MUST return a string object (str). Returning an integer, list, or printing to the console with print() inside them causes a fatal TypeError: __str__ returned non-string.
Create a ShoppingBag class that holds a list of items. Implement __len__() to return total item count, and __str__() to format the bag contents.
class ShoppingBag:
def __init__(self):
self.items = []
def add(self, item):
self.items.append(item)
def __len__(self):
return len(self.items)
def __str__(self):
return f"ShoppingBag with {len(self)} items: {', '.join(self.items)}"
bag = ShoppingBag()
bag.add("Laptop")
bag.add("Mouse")
bag.add("Keyboard")
print(bag)
print("Item count via len():", len(bag))
Q What is the difference between an Abstract Class and an Interface in Python?
In Python, there is no separate "interface" keyword. Abstract Base Classes (ABCs) serve as interfaces by defining abstract methods with no body (@abstractmethod), while also allowing partial implementation of shared concrete helper methods.
Q What happens if a subclass does not implement all @abstractmethods?
Python raises TypeError: Can't instantiate abstract class SubClass with abstract method ... when attempting to instantiate the subclass.
Q What is the purpose of __getitem__ and __setitem__?
__getitem__ and __setitem__ allow custom objects to support square-bracket indexing and dictionary-like access: obj[key] and obj[key] = value.