Abstract Classes
Define interfaces with ABC and @abstractmethod — guarantee that every child class implements the methods it promises to.
"An abstract class is a contract. It says: every class that inherits from me MUST implement these methods. No exceptions."
— ShurAIThe Problem: Forgetting to Implement Methods
When you build an inheritance hierarchy, a child class might forget to implement a critical method. You only find out when it crashes at runtime — too late:
class Shape:
def area(self):
pass # child SHOULD override this, but nothing forces it
class Square(Shape):
pass # forgot area() — Python happily creates it
s = Square()
s.area() # returns None silently — problem found too late
ABC — Enforcing the Contract
Inherit from ABC and mark required methods with @abstractmethod. Python will refuse to create any class that doesn’t implement all abstract methods:
from abc import ABC, abstractmethod
class Shape(ABC): # inherit from ABC
@abstractmethod
def area(self) -> float: # every child MUST implement this
pass
@abstractmethod
def perimeter(self) -> float:
pass
# Trying to instantiate Shape directly raises TypeError:
# Shape() → TypeError: Can't instantiate abstract class Shape
# A child that forgets area() also raises TypeError at creation:
class BadSquare(Shape):
def perimeter(self): return 0
# BadSquare() → TypeError: Can't instantiate — area() not implemented
@abstractmethod: area(), perimeter()
Implementing the Contract
import math
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
return math.pi * self.radius ** 2
def perimeter(self) -> float:
return 2 * math.pi * self.radius
class Rectangle(Shape):
def __init__(self, w: float, h: float):
self.w, self.h = w, h
def area(self) -> float:
return self.w * self.h
def perimeter(self) -> float:
return 2 * (self.w + self.h)
shapes = [Circle(5), Rectangle(4, 6)]
for s in shapes:
print(f"{s.__class__.__name__}: area={s.area():.2f}, perimeter={s.perimeter():.2f}")
Circle: area=78.54, perimeter=31.42
Rectangle: area=24.00, perimeter=20.00
Real Example — Payment Gateway Interface
from abc import ABC, abstractmethod
class PaymentGateway(ABC):
@abstractmethod
def charge(self, amount: float) -> bool: pass
@abstractmethod
def refund(self, amount: float) -> bool: pass
def receipt(self, amount: float):
print(f"Receipt: {self.__class__.__name__} charged {amount}")
class StripeGateway(PaymentGateway):
def charge(self, amount):
print(f"Stripe: charging {amount}")
return True
def refund(self, amount):
print(f"Stripe: refunding {amount}")
return True
class RazorpayGateway(PaymentGateway):
def charge(self, amount):
print(f"Razorpay: charging {amount}")
return True
def refund(self, amount):
print(f"Razorpay: refunding {amount}")
return True
for gw in [StripeGateway(), RazorpayGateway()]:
gw.charge(999)
gw.receipt(999) # concrete method shared by all gateways
Stripe: charging 999
Receipt: StripeGateway charged 999
Razorpay: charging 999
Receipt: RazorpayGateway charged 999
"ABCs are great for plugin systems and APIs: you define what every plugin must do, ship the abstract class, and anyone building a plugin is guided exactly by what they must implement."
— ShurAI🧠 Quiz — Q1
What happens when you try to instantiate an abstract class directly?
🧠 Quiz — Q2
What does marking a method with @abstractmethod do?
🧠 Quiz — Q3
An abstract class Animal has abstract method speak(). Class Dog(Animal) does not implement speak(). What happens when you write Dog()?
🧠 Quiz — Q4
What is the difference between an abstract method and a regular method in an ABC?