- What is an abstract class โ a class that can't be instantiated and can have both abstract and concrete methods
- What is an interface โ a contract that defines what methods a class must have
- Key differences โ state, implementation, inheritance, and enforcement
- When to use each โ practical guidance for your code
- Real-world examples โ see both concepts in action
- Python-specific โ how ABCs and Protocols implement these concepts
What's the Difference?
In many programming languages like Java and C#, abstract classes and interfaces are clearly distinct concepts with specific syntax. In Python, the lines are blurrier because Python doesn't have a dedicated interface keyword.
However, Python does provide tools to implement both concepts:
- Abstract Classes โ created using
abc.ABCand@abstractmethod - Interfaces โ implemented using Abstract Base Classes (with only abstract methods) or Protocols (structural typing)
๐ก Key concept: The main difference is what they can contain and how they're used. Abstract classes can have state and concrete methods; interfaces are pure contracts with no implementation.
Abstract Classes in Python
What is an Abstract Class?
An abstract class is a class that cannot be instantiated. It serves as a blueprint for other classes. Abstract classes can contain:
- Abstract methods โ methods without implementation that must be implemented by subclasses
- Concrete methods โ methods with implementation that subclasses can use or override
- Instance variables โ state that subclasses inherit
- Constructors โ
__init__methods that initialize state
Abstract classes are great when you have shared behavior and common state that multiple related classes should inherit.
# Abstract Class in Python
from abc import ABC, abstractmethod
class Animal(ABC):
"""Abstract base class for all animals"""
def __init__(self, name, age):
self.name = name
self.age = age
@abstractmethod
def make_sound(self):
"""Abstract method โ must be implemented by subclasses"""
pass
@abstractmethod
def move(self):
"""Abstract method โ must be implemented by subclasses"""
pass
# Concrete method โ shared by all subclasses
def get_info(self):
return f"{self.name} is {self.age} years old"
# Another concrete method
def sleep(self):
return f"{self.name} is sleeping"
class Dog(Animal):
def make_sound(self):
return "Woof!"
def move(self):
return f"{self.name} runs on four legs"
# Optional: override the concrete method
def sleep(self):
return f"{self.name} is sleeping (curled up)"
class Cat(Animal):
def make_sound(self):
return "Meow!"
def move(self):
return f"{self.name} walks silently"
def sleep(self):
return f"{self.name} is sleeping (on the couch)"
# Cannot instantiate abstract class
# animal = Animal("Bob", 5) # TypeError
# But can instantiate subclasses
dog = Dog("Rex", 3)
cat = Cat("Whiskers", 2)
print(dog.get_info()) # Rex is 3 years old
print(dog.make_sound()) # Woof!
print(dog.move()) # Rex runs on four legs
print(dog.sleep()) # Rex is sleeping (curled up)
print(cat.get_info()) # Whiskers is 2 years old
print(cat.make_sound()) # Meow!
print(cat.move()) # Whiskers walks silently
print("\nโ
Abstract classes: can have state, abstract methods, and concrete methods")
print("โ ๏ธ Cannot instantiate the abstract class itself")
Abstract class key points:
- Can have state โ instance variables are inherited
- Can have concrete methods โ shared behavior
- Must have at least one abstract method โ to be abstract
- Cannot be instantiated โ used as a template
- Single inheritance โ a class can inherit from only one abstract class (in most cases)
Quick Check: What two types of methods can an abstract class have? (Answer: Abstract methods and concrete methods)
Interfaces in Python
What is an Interface?
An interface is a contract that defines what methods a class must have, but not how they work. In Python, interfaces are implemented in two ways:
- Using Abstract Base Classes with only abstract methods (no concrete methods, no state)
- Using Protocols (structural typing, no inheritance required)
Interfaces are great when you want to define a contract that multiple unrelated classes can implement. They focus on what the class can do, not what it is.
# Interfaces in Python โ Two Approaches
from abc import ABC, abstractmethod
from typing import Protocol
# -----------------------------
# Approach 1: ABC with only abstract methods
# -----------------------------
class Drawable(ABC):
"""Interface: anything that can be drawn"""
@abstractmethod
def draw(self):
"""Draw the object โ must be implemented"""
pass
@abstractmethod
def get_area(self):
"""Return the area โ must be implemented"""
pass
class Circle(Drawable):
def __init__(self, radius):
self.radius = radius
def draw(self):
return f"Drawing a circle with radius {self.radius}"
def get_area(self):
return 3.14 * self.radius ** 2
class Square(Drawable):
def __init__(self, side):
self.side = side
def draw(self):
return f"Drawing a square with side {self.side}"
def get_area(self):
return self.side ** 2
# -----------------------------
# Approach 2: Protocol (structural typing)
# -----------------------------
class Renderable(Protocol):
"""Interface: anything that can be rendered"""
def render(self) -> str: ...
def get_name(self) -> str: ...
# These classes don't need to inherit from anything
class PDFRenderer:
def render(self) -> str:
return "Rendering PDF file"
def get_name(self) -> str:
return "PDF Renderer"
class HTMLRenderer:
def render(self) -> str:
return "Rendering HTML content"
def get_name(self) -> str:
return "HTML Renderer"
# -----------------------------
# Using both approaches
# -----------------------------
print("=== ABC Interface (Drawable) ===")
circle = Circle(5)
square = Square(4)
print(circle.draw()) # Drawing a circle with radius 5
print(f"Area: {circle.get_area()}") # Area: 78.5
print(square.draw()) # Drawing a square with side 4
print(f"Area: {square.get_area()}") # Area: 16
print("\n=== Protocol Interface (Renderable) ===")
def display(renderer: Renderable):
print(f"{renderer.get_name()}: {renderer.render()}")
display(PDFRenderer()) # PDF Renderer: Rendering PDF file
display(HTMLRenderer()) # HTML Renderer: Rendering HTML content
print("\nโ
Interfaces: pure contract, no state, no implementation")
print("โ
Protocol: classes don't need to inherit from anything")
Interface key points:
- No state โ no instance variables
- No concrete methods โ only abstract method signatures
- Pure contract โ defines what methods must exist
- Multiple inheritance โ a class can implement multiple interfaces
- Protocols โ structural typing, no inheritance required
Quick Check: Can an interface have instance variables? (Answer: No โ interfaces only define methods, not state)
Key Differences
Abstract Class vs Interface โ Side by Side
# Abstract Class vs Interface โ Side by Side Comparison
from abc import ABC, abstractmethod
from typing import Protocol
# ----- ABSTRACT CLASS -----
class Vehicle(ABC):
"""Abstract class โ can have state and concrete methods"""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.speed = 0
@abstractmethod
def start_engine(self):
"""Abstract method โ must be implemented"""
pass
@abstractmethod
def stop_engine(self):
"""Abstract method โ must be implemented"""
pass
# Concrete method โ shared behavior
def accelerate(self, amount):
self.speed += amount
return f"Accelerating to {self.speed} km/h"
# Concrete method โ shared behavior
def get_info(self):
return f"{self.make} {self.model} ({self.year})"
class Car(Vehicle):
def start_engine(self):
return "Car engine started with key"
def stop_engine(self):
return "Car engine stopped"
class Motorcycle(Vehicle):
def start_engine(self):
return "Motorcycle engine started with kick"
def stop_engine(self):
return "Motorcycle engine stopped"
# ----- INTERFACE (using ABC with only abstract methods) -----
class Drivable(ABC):
"""Interface โ pure contract, no state, no concrete methods"""
@abstractmethod
def drive(self, distance):
pass
@abstractmethod
def brake(self):
pass
class Sedan(Drivable):
def __init__(self, make, model):
self.make = make
self.model = model
def drive(self, distance):
return f"Driving {distance} km in sedan"
def brake(self):
return "Applying brakes in sedan"
class SUV(Drivable):
def __init__(self, make, model):
self.make = make
self.model = model
def drive(self, distance):
return f"Driving {distance} km in SUV"
def brake(self):
return "Applying brakes in SUV"
# ----- INTERFACE (using Protocol) -----
class Brakeable(Protocol):
"""Interface โ structural typing"""
def brake(self) -> str: ...
def apply_emergency_brake(vehicle: Brakeable):
print(f"โ ๏ธ EMERGENCY: {vehicle.brake()}")
# Both Sedan and SUV work with the Protocol
car1 = Sedan("Toyota", "Camry")
car2 = SUV("Honda", "CR-V")
apply_emergency_brake(car1) # Applying brakes in sedan
apply_emergency_brake(car2) # Applying brakes in SUV
# ----- COMPARISON SUMMARY -----
print("\n" + "=" * 60)
print("ABSTRACT CLASS vs INTERFACE")
print("=" * 60)
print("\n| Feature | Abstract Class | Interface |")
print("|----------------------|-----------------------|-----------------------|")
print("| State (variables) | โ
Yes | โ No |")
print("| Concrete methods | โ
Yes | โ No |")
print("| Abstract methods | โ
Yes | โ
Yes |")
print("| Instantiable? | โ No | โ No |")
print("| Multiple inheritance | โ Usually one | โ
Yes |")
print("| Use case | Share code & state | Define contract |")
print("| Python tools | abc.ABC, @abstractmethod | abc.ABC or Protocol |")
print("\nโ
Abstract class: 'IS-A' relationship (what something IS)")
print("โ
Interface: 'CAN-DO' relationship (what something CAN DO)")
Key differences summary:
- State: Abstract classes can have instance variables; interfaces cannot
- Concrete methods: Abstract classes can have implemented methods; interfaces cannot
- Inheritance: Abstract classes support single inheritance; interfaces support multiple
- Relationship: Abstract class = "IS-A"; Interface = "CAN-DO"
- Use case: Abstract class = share code; Interface = define a contract
Quick Check: What's the main difference in terms of state? (Answer: Abstract classes can have instance variables; interfaces cannot)
When to Use What
Making the Right Choice
Here's a simple rule of thumb for choosing between an abstract class and an interface:
# When to Use Abstract Class vs Interface
# ----- USE ABSTRACT CLASS WHEN -----
# 1. You need to share common code among related classes
class Shape(ABC):
def __init__(self, color):
self.color = color
@abstractmethod
def area(self):
pass
# Concrete method โ shared by all shapes
def get_color(self):
return self.color
class Rectangle(Shape):
def __init__(self, color, width, height):
super().__init__(color)
self.width = width
self.height = height
def area(self):
return self.width * self.height
# โ
Abstract class: Rectangle IS-A Shape, shares color state and get_color()
# 2. You want to provide a default implementation
class Writer(ABC):
@abstractmethod
def write(self, content):
pass
# Default implementation that can be overridden
def write_line(self, content):
return self.write(content + "\n")
class FileWriter(Writer):
def write(self, content):
return f"Writing: {content}"
class ConsoleWriter(Writer):
def write(self, content):
return f"Console: {content}"
# โ
Abstract class: provides default write_line() that uses write()
# ----- USE INTERFACE WHEN -----
# 1. You want to define a contract for unrelated classes
class Flyable(ABC):
@abstractmethod
def fly(self):
pass
class Bird:
def fly(self):
return "Bird flies"
class Airplane:
def fly(self):
return "Airplane flies"
# โ
Interface: Bird and Airplane are unrelated but both CAN-DO fly()
# 2. You want multiple inheritance (implement multiple interfaces)
class Swimmable(ABC):
@abstractmethod
def swim(self):
pass
class Amphibian(Flyable, Swimmable):
def fly(self):
return "Amphibian flies"
def swim(self):
return "Amphibian swims"
# โ
Interface: multiple inheritance is clean and safe
# ----- USE PROTOCOL (modern interface) WHEN -----
# 3. You want loose coupling (class doesn't depend on your interface)
class Serializer(Protocol):
def serialize(self) -> str: ...
class JSONData:
def serialize(self) -> str:
return '{"data": "json"}'
class XMLData:
def serialize(self) -> str:
return "xml"
def save(data: Serializer):
print(f"Saving: {data.serialize()}")
# โ
Protocol: classes don't need to import or inherit from Serializer
print("""
| Use Case | Abstract Class | Interface (ABC) | Protocol |
|-----------------------------------|----------------|-----------------|----------|
| Share code & state | โ
Yes | โ No | โ No |
| Define contract for related classes | โ
Yes | โ
Yes | โ
Yes |
| Define contract for unrelated classes | โ No | โ
Yes | โ
Yes |
| Multiple inheritance | โ No | โ
Yes | โ
Yes |
| Loose coupling (no dependency) | โ No | โ No | โ
Yes |
| Runtime enforcement | โ
Yes | โ
Yes | โ No* |
| Static type checking | โ
Yes | โ
Yes | โ
Yes |
| Default implementations | โ
Yes | โ No | โ No |
""")
print("* Protocols can use @runtime_checkable for runtime checks")
Decision guide:
- Abstract class โ use when classes are closely related and share code/state
- Interface (ABC) โ use when defining a contract for unrelated classes
- Interface (Protocol) โ use when you want loose coupling and structural typing
- Multiple inheritance โ interfaces are safer than abstract classes
- Default implementations โ only abstract classes can provide these
Quick Check: When would you choose an abstract class over an interface? (Answer: When you need to share code and state among closely related classes)
Real-World Examples
Abstract Class and Interface in Action
# Real-World Example: Payment Processing System
from abc import ABC, abstractmethod
from typing import Protocol
import json
# ============================================================
# Part 1: ABSTRACT CLASS โ Base Payment Processor
# ============================================================
class PaymentProcessor(ABC):
"""Abstract class for all payment processors"""
def __init__(self, merchant_id: str):
self.merchant_id = merchant_id
self.transactions = []
@abstractmethod
def process_payment(self, amount: float, currency: str) -> dict:
"""Process a payment โ must be implemented"""
pass
@abstractmethod
def refund_payment(self, transaction_id: str) -> dict:
"""Refund a payment โ must be implemented"""
pass
# Concrete method โ shared by all processors
def get_transaction_history(self):
return self.transactions
# Concrete method โ shared by all processors
def add_transaction(self, transaction):
self.transactions.append(transaction)
return transaction
class StripeProcessor(PaymentProcessor):
def process_payment(self, amount: float, currency: str) -> dict:
transaction = {
"id": f"stripe_{len(self.transactions) + 1}",
"amount": amount,
"currency": currency,
"status": "succeeded",
"processor": "Stripe"
}
self.add_transaction(transaction)
return transaction
def refund_payment(self, transaction_id: str) -> dict:
return {
"id": f"refund_{transaction_id}",
"status": "refunded",
"processor": "Stripe"
}
class PayPalProcessor(PaymentProcessor):
def process_payment(self, amount: float, currency: str) -> dict:
transaction = {
"id": f"paypal_{len(self.transactions) + 1}",
"amount": amount,
"currency": currency,
"status": "completed",
"processor": "PayPal"
}
self.add_transaction(transaction)
return transaction
def refund_payment(self, transaction_id: str) -> dict:
return {
"id": f"refund_{transaction_id}",
"status": "refunded",
"processor": "PayPal"
}
# ============================================================
# Part 2: INTERFACE โ Notification System (Protocol)
# ============================================================
class Notifier(Protocol):
"""Protocol for notification senders"""
def send(self, message: str, recipient: str) -> dict: ...
def get_status(self) -> str: ...
# These classes don't know about the Notifier protocol
class EmailNotifier:
def send(self, message: str, recipient: str) -> dict:
return {
"status": "sent",
"recipient": recipient,
"message": message[:20] + "...",
"method": "email"
}
def get_status(self) -> str:
return "Email service: healthy"
class SMSNotifier:
def send(self, message: str, recipient: str) -> dict:
return {
"status": "sent",
"recipient": recipient,
"message": message[:15] + "...",
"method": "sms"
}
def get_status(self) -> str:
return "SMS service: healthy"
class PushNotifier:
def send(self, message: str, recipient: str) -> dict:
return {
"status": "delivered",
"recipient": recipient,
"message": message[:10] + "...",
"method": "push"
}
def get_status(self) -> str:
return "Push service: healthy"
# ============================================================
# Part 3: USING THE SYSTEM
# ============================================================
def process_order(amount: float, currency: str,
processor: PaymentProcessor,
notifiers: list):
"""Process an order with payment and notifications"""
# Process payment (using abstract class)
result = processor.process_payment(amount, currency)
print(f"๐ฐ Payment: {result}")
# Send notifications (using protocol)
for notifier in notifiers:
status = notifier.get_status()
if "healthy" in status or "delivered" in status:
notification = notifier.send(
f"Your payment of {amount} {currency} was processed",
"customer@example.com"
)
print(f"๐จ {notification}")
return result
# Demo
print("=" * 60)
print("PAYMENT PROCESSING SYSTEM")
print("=" * 60)
stripe = StripeProcessor("merchant_001")
paypal = PayPalProcessor("merchant_002")
notifiers = [EmailNotifier(), SMSNotifier(), PushNotifier()]
print("\n1. Processing with Stripe")
process_order(99.99, "USD", stripe, notifiers)
print("\n2. Processing with PayPal")
process_order(49.95, "EUR", paypal, notifiers)
print("\n3. Transaction History")
print(f"Stripe transactions: {stripe.get_transaction_history()}")
print(f"PayPal transactions: {paypal.get_transaction_history()}")
print("\nโ
Abstract class: PaymentProcessor provides shared state and methods")
print("โ
Protocol: Notifier allows any class with send() and get_status()")
Real-world example key points:
- Abstract class (PaymentProcessor) โ shares state (merchant_id, transactions) and concrete methods (get_transaction_history, add_transaction)
- Concrete processors (StripeProcessor, PayPalProcessor) โ implement the abstract methods
- Interface (Notifier Protocol) โ defines what a notifier can do without requiring inheritance
- Concrete notifiers (Email, SMS, Push) โ don't depend on the protocol, just have the right methods
- Both approaches โ work together in the same system
Quick Check: In the example, what does the abstract class provide that the interface doesn't? (Answer: State (merchant_id, transactions) and concrete methods (get_transaction_history, add_transaction))
Best Practices
Using Abstract Classes and Interfaces Effectively
# Best Practices for Abstract Classes and Interfaces
from abc import ABC, abstractmethod
from typing import Protocol
# ============================================================
# 1. ABSTRACT CLASS BEST PRACTICES
# ============================================================
# โ
DO: Use abstract classes for related classes with shared code
class Animal(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def speak(self):
pass
# Shared concrete method
def get_name(self):
return self.name
# โ DON'T: Use abstract class for unrelated classes
# class Printable(ABC): # This should be an interface
# @abstractmethod
# def print(self):
# pass
# ============================================================
# 2. INTERFACE BEST PRACTICES
# ============================================================
# โ
DO: Keep interfaces focused (single responsibility)
class Printable(ABC):
@abstractmethod
def print(self):
pass
class Serializable(ABC):
@abstractmethod
def serialize(self):
pass
# โ DON'T: Put unrelated methods in one interface
# class Everything(ABC):
# @abstractmethod
# def print(self):
# pass
# @abstractmethod
# def serialize(self):
# pass
# @abstractmethod
# def save(self):
# pass
# ============================================================
# 3. MIXING ABSTRACT CLASSES AND INTERFACES
# ============================================================
# โ
DO: Use abstract class for core behavior and interfaces for additional capabilities
class Document(ABC):
"""Abstract class: core document behavior"""
def __init__(self, title):
self.title = title
@abstractmethod
def open(self):
pass
@abstractmethod
def close(self):
pass
class Printable:
"""Interface: can be printed"""
def print(self):
return f"Printing {self.title}"
class Exportable:
"""Interface: can be exported"""
def export(self):
return f"Exporting {self.title}"
class PDFDocument(Document, Printable):
def open(self):
return f"Opening PDF: {self.title}"
def close(self):
return f"Closing PDF: {self.title}"
class WordDocument(Document, Printable, Exportable):
def open(self):
return f"Opening Word: {self.title}"
def close(self):
return f"Closing Word: {self.title}"
# ============================================================
# 4. INTERFACE NAMING CONVENTIONS
# ============================================================
# โ
DO: Use clear names โ suffix with "-able" for capabilities
class Playable(ABC):
"""Interface: something that can be played"""
@abstractmethod
def play(self):
pass
class Stoppable(ABC):
"""Interface: something that can be stopped"""
@abstractmethod
def stop(self):
pass
# โ DON'T: Use vague names
# class Doer(ABC):
# pass
# ============================================================
# 5. PROTOCOL BEST PRACTICES
# ============================================================
# โ
DO: Use Protocols for loose coupling
class Logger(Protocol):
def log(self, message: str) -> None: ...
class FileLogger:
def log(self, message: str) -> None:
print(f"File: {message}")
class ConsoleLogger:
def log(self, message: str) -> None:
print(f"Console: {message}")
# โ
DO: Use Protocols for public APIs
class Cache(Protocol):
def get(self, key: str) -> object: ...
def set(self, key: str, value: object) -> None: ...
# ============================================================
# 6. SUMMARY
# ============================================================
print("""
๐ BEST PRACTICES SUMMARY
========================
ABSTRACT CLASS:
โข Use for closely related classes
โข Share code and state
โข Use single inheritance
โข Name clearly (Animal, PaymentProcessor, etc.)
INTERFACE (ABC):
โข Use for unrelated classes
โข Define contract only (no state, no implementation)
โข Support multiple inheritance
โข Name with "-able" suffix (Printable, Serializable)
INTERFACE (PROTOCOL):
โข Use for loose coupling
โข Class doesn't depend on the interface
โข Use for structural typing
โข Great for public APIs and third-party code
MIXING:
โข Abstract class for core behavior
โข Interfaces for additional capabilities
โข Both can be used in the same class
""")
Best practices summary:
- Abstract classes โ for related classes that share code and state
- Interfaces (ABC) โ for unrelated classes that need a contract
- Interfaces (Protocol) โ for loose coupling and structural typing
- Keep interfaces focused โ single responsibility
- Use clear naming โ "-able" suffix for interfaces
- Mix both โ abstract class for core behavior, interfaces for additional capabilities
Quick Check: What's a good naming convention for interfaces? (Answer: Use the "-able" suffix, like Printable, Serializable, Playable)
Try It Yourself
Experiment with abstract classes and interfaces in the editor below.
ABSTRACT CLASS vs INTERFACE - PRACTICE
==================================================
1. DEFINING AN ABSTRACT CLASS
2. IMPLEMENTING THE ABSTRACT CLASS
3. DEFINING AN INTERFACE (PROTOCOL)
4. CLASSES SATISFYING THE PROTOCOL
5. USING ABSTRACT CLASS AND INTERFACE
=== Appliances (Abstract Class) ===
LG WM-100
๐งบ Washing machine is ON
๐งบ Washing machine is OFF
Samsung RF-200
โ๏ธ Refrigerator is ON (cooling)
โ๏ธ Refrigerator is OFF
=== Smart Devices (Protocol) ===
Controlling Smart Washer:
Status: OFF
๐งบ Washing machine is ON
Status: ON
๐งบ Washing machine is OFF
Status: OFF
Controlling Smart Fridge:
Status: Idle
โ๏ธ Refrigerator is ON (cooling)
Status: Cooling
โ๏ธ Refrigerator is OFF
Status: Idle
โ Abstract class: shares state and concrete methods
โ Protocol: no inheritance needed, just have the right methods
You've Got It!
You now understand the differences between abstract classes and interfaces in Python. You know when to use each and how to implement them using ABCs and Protocols.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
Is there a difference between an abstract class and an interface in Python?
Can a class inherit from both an abstract class and an interface?
class MyClass(AbstractClass, Interface1, Interface2):.
When should I use an abstract class vs an interface?
Can an abstract class have a constructor?
__init__ methods) that initialize state. Subclasses typically call the abstract class constructor using super().__init__().
What's a common interview question about abstract classes vs interfaces?
Are Protocols and interfaces the same thing?
Where to Go From Here
Now that you understand the differences between abstract classes and interfaces, check out these related topics:
Abstraction in Python
Learn more about abstraction and how it's implemented in Python.
Learn More โInterfaces in Python
Deep dive into interfaces using ABCs and Protocols.
Learn More โPolymorphism
Learn how abstract classes and interfaces enable polymorphic behavior.
Learn More โ