- What is an abstract method ā a method without implementation that must be overridden
- Why we need them ā enforcing a contract for child classes
- The @abstractmethod decorator ā how to declare abstract methods
- Implementing abstract methods ā providing concrete implementations
- Concrete vs abstract ā understanding the difference
- Real-world use ā practical examples you can use
What is an Abstract Method?
An abstract method is a method that is declared but not implemented in a class. It's like a placeholder ā it tells the world "this method exists, but I'm not going to write the code for it here." The actual code must be provided by any class that inherits from this class.
Think of it like a job description. A job description says "the person in this role must be able to do X, Y, and Z." It doesn't tell you exactly how to do those things ā it's up to the person who takes the job to figure that out. An abstract method is exactly that ā a requirement that must be fulfilled.
In Python, abstract methods are created using the @abstractmethod decorator from the abc module. They can only exist inside abstract classes.
š” Key concept: An abstract method is a promise ā "I promise this method exists, but I'm not implementing it here. You must implement it in your class." It enforces a contract between the abstract class and its children.
Why Do We Need Abstract Methods?
Enforcing a Contract
Abstract methods exist to enforce a contract. They say: "If you want to be a child of this class, you MUST have these methods." This ensures that all child classes have the same interface, making your code more predictable and easier to work with.
Without abstract methods, you might forget to implement a method in a child class ā and your code would break at runtime. Abstract methods catch these errors early when you try to create an object, not when you try to use the missing method.
# Why abstract methods are important
from abc import ABC, abstractmethod
# Abstract class with abstract methods
class Worker(ABC):
"""A worker must be able to work and rest"""
@abstractmethod
def work(self):
"""Every worker must work ā no exception"""
pass
@abstractmethod
def rest(self):
"""Every worker must rest ā no exception"""
pass
# A good child class ā implements all abstract methods
class Programmer(Worker):
def work(self):
return "Writing code"
def rest(self):
return "Taking a coffee break"
# Another good child class
class Teacher(Worker):
def work(self):
return "Teaching students"
def rest(self):
return "Grading papers"
# A bad child class ā forgets to implement a method
# This would cause an error when you try to create it!
class BadWorker(Worker):
def work(self):
return "Working"
# Missing rest() method
# Using the good classes
programmer = Programmer()
teacher = Teacher()
print("=== PROGRAMMER ===")
print(programmer.work())
print(programmer.rest())
print("\n=== TEACHER ===")
print(teacher.work())
print(teacher.rest())
# This would cause an error:
# bad = BadWorker() # TypeError: Can't instantiate abstract class BadWorker with abstract method rest
print("\nā
Abstract methods catch missing implementations EARLY!")
print("Before you even use the object, Python tells you what's missing.")
Why abstract methods matter:
- Enforce contracts ā child classes must implement certain methods
- Early error detection ā catch missing implementations at instantiation time
- Consistent interface ā all child classes have the same methods
- Better code quality ā prevents runtime errors from missing methods
- Design clarity ā clearly shows what methods are required
Quick Check: What happens if a child class doesn't implement an abstract method? (Answer: You can't instantiate the child class ā Python raises a TypeError)
The @abstractmethod Decorator
How to Declare Abstract Methods
In Python, you declare an abstract method using the @abstractmethod decorator. This decorator tells Python: "This method is abstract ā it has no implementation and must be overridden by child classes."
The @abstractmethod decorator is part of the abc module (ABC stands for Abstract Base Class). You need to import it along with the ABC class that your abstract class inherits from.
# Using the @abstractmethod decorator
from abc import ABC, abstractmethod
# Correct way to create an abstract class with abstract methods
class Shape(ABC):
"""Abstract class with abstract methods"""
@abstractmethod
def area(self):
"""Calculate the area ā must be implemented"""
pass
@abstractmethod
def perimeter(self):
"""Calculate the perimeter ā must be implemented"""
pass
# Concrete method ā not abstract, can be used as-is
def describe(self):
return f"This is a shape"
# The @abstractmethod decorator can be used with other decorators
class Animal(ABC):
@abstractmethod
def sound(self):
"""Make a sound ā must be implemented"""
pass
@abstractmethod
def move(self):
"""Move ā must be implemented"""
pass
# You can also combine @abstractmethod with @classmethod or @staticmethod
class Repository(ABC):
@abstractmethod
def save(self, data):
"""Save data ā must be implemented"""
pass
@abstractmethod
def get(self, id):
"""Get data by id ā must be implemented"""
pass
@classmethod
@abstractmethod
def create_empty(cls):
"""Create an empty repository ā must be implemented"""
pass
# Implementing the abstract methods
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Dog(Animal):
def sound(self):
return "Woof!"
def move(self):
return "Runs on four legs"
# Testing
rect = Rectangle(5, 3)
print(f"Area: {rect.area()}") # 15
print(f"Perimeter: {rect.perimeter()}") # 16
print(rect.describe()) # This is a shape (concrete method)
dog = Dog()
print(dog.sound()) # Woof!
print(dog.move()) # Runs on four legs
print("\nā
@abstractmethod creates a contract that child classes must fulfill!")
print("The decorator tells Python this method is required in child classes.")
@abstractmethod key points:
- From abc module ā must import @abstractmethod and ABC
- No implementation ā abstract methods only have a signature (and docstring)
- Use pass ā the body is typically just 'pass'
- Can be combined ā works with @classmethod and @staticmethod
- Child must implement ā or it remains abstract
Quick Check: What two things must you import from the abc module? (Answer: ABC and abstractmethod)
Implementing Abstract Methods
Providing Concrete Implementations
When a child class inherits from an abstract class with abstract methods, it must implement all of them. If it doesn't, the child class will also be abstract and you won't be able to create objects from it.
The implementation can be anything ā as long as the method has the same name and a body. The abstract class doesn't care what you do, it just cares that you did something.
# Implementing abstract methods
from abc import ABC, abstractmethod
import math
class Shape(ABC):
"""Abstract class ā defines what a shape must do"""
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
@abstractmethod
def get_name(self):
pass
# 1. Fully implemented child class
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
def get_name(self):
return "Circle"
# 2. Another fully implemented child class
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
def perimeter(self):
return 4 * self.side
def get_name(self):
return "Square"
# 3. Partially implemented child class (missing a method)
class Triangle(Shape):
def __init__(self, base, height, side1, side2, side3):
self.base = base
self.height = height
self.side1 = side1
self.side2 = side2
self.side3 = side3
def area(self):
return 0.5 * self.base * self.height
# Missing perimeter() ā this will cause an error when instantiating
# Missing get_name() ā this will cause an error when instantiating
# 4. Child class that extends abstract class but doesn't implement all methods
class AbstractChild(Shape):
def area(self):
return 0
# Missing perimeter() and get_name() ā can't instantiate
# Using fully implemented classes
print("=== CIRCLE ===")
circle = Circle(5)
print(f"Name: {circle.get_name()}")
print(f"Area: {circle.area():.2f}")
print(f"Perimeter: {circle.perimeter():.2f}")
print("\n=== SQUARE ===")
square = Square(4)
print(f"Name: {square.get_name()}")
print(f"Area: {square.area()}")
print(f"Perimeter: {square.perimeter()}")
# This would cause an error:
# triangle = Triangle(10, 5, 3, 4, 5) # TypeError: Can't instantiate abstract class Triangle
print("\nā
You must implement ALL abstract methods to create a concrete class!")
print("Missing even one makes the class abstract and uninstantiable.")
Implementing abstract methods key points:
- All must be implemented ā every abstract method must have a concrete implementation
- Same name required ā the method name must match exactly
- Any implementation works ā as long as there's a body
- Missing any = abstract ā if one is missing, you can't create objects
- Check at instantiation ā Python checks when you try to create an object
Quick Check: What happens if a child class implements only some abstract methods? (Answer: The child class remains abstract and can't be instantiated)
Concrete vs Abstract Methods
Understanding the Difference
The main difference between concrete and abstract methods is implementation. A concrete method has a body ā it actually does something. An abstract method has no body ā it's just a placeholder.
Abstract methods are like empty containers waiting to be filled. Concrete methods are already full and ready to use. Abstract classes can have both ā this gives you the best of both worlds: enforced requirements and shared functionality.
# Concrete methods vs Abstract methods
from abc import ABC, abstractmethod
class Document(ABC):
"""Abstract class with both abstract and concrete methods"""
# Abstract method ā no implementation
@abstractmethod
def save(self):
"""Save the document ā must be implemented"""
pass
# Abstract method ā no implementation
@abstractmethod
def open(self):
"""Open the document ā must be implemented"""
pass
# Concrete method ā has implementation
def get_extension(self):
"""Get the file extension ā shared by all documents"""
return ".docx"
# Concrete method ā has implementation
def get_size(self):
"""Get the document size ā shared by all documents"""
return "100 KB"
# Concrete method ā has implementation
def print_info(self):
"""Print document info ā shared by all documents"""
return f"Document (size: {self.get_size()})"
class PDFDocument(Document):
"""Concrete class ā implements all abstract methods"""
def save(self):
return "Saving PDF document"
def open(self):
return "Opening PDF document"
# Override concrete method if needed
def get_extension(self):
return ".pdf"
class WordDocument(Document):
"""Concrete class ā implements all abstract methods"""
def save(self):
return "Saving Word document"
def open(self):
return "Opening Word document"
# Uses parent's concrete methods as-is
# Testing
print("=== PDF DOCUMENT ===")
pdf = PDFDocument()
print(pdf.save()) # Abstract method implemented
print(pdf.open()) # Abstract method implemented
print(pdf.get_extension()) # Concrete method overridden
print(pdf.get_size()) # Concrete method inherited
print(pdf.print_info()) # Concrete method inherited
print("\n=== WORD DOCUMENT ===")
word = WordDocument()
print(word.save()) # Abstract method implemented
print(word.open()) # Abstract method implemented
print(word.get_extension()) # Concrete method inherited
print(word.get_size()) # Concrete method inherited
print(word.print_info()) # Concrete method inherited
print("\nā
Abstract methods = no body (must be overridden)")
print("ā
Concrete methods = has body (can be used directly)")
print("Abstract classes can have BOTH types!")
print(" - Abstract methods enforce the contract")
print(" - Concrete methods provide shared functionality")
Concrete vs Abstract key points:
- Abstract method ā has no body, only signature (use pass)
- Concrete method ā has a body with actual code
- Abstract methods must be overridden ā by all concrete child classes
- Concrete methods can be used as-is ā or overridden if needed
- Both can exist in the same class ā mix and match as needed
Quick Check: What's the difference between an abstract method and a concrete method? (Answer: Abstract methods have no body; concrete methods have a full implementation)
Real-World Examples
Seeing Abstract Methods in Action
# Real-world example: A Data Export System
from abc import ABC, abstractmethod
class DataExporter(ABC):
"""Abstract class ā defines how to export data"""
@abstractmethod
def connect(self):
"""Connect to the data source ā must be implemented"""
pass
@abstractmethod
def fetch_data(self):
"""Fetch the data ā must be implemented"""
pass
@abstractmethod
def format_data(self, data):
"""Format the data for export ā must be implemented"""
pass
@abstractmethod
def export(self, data, destination):
"""Export the data ā must be implemented"""
pass
# Concrete method ā shared by all exporters
def log_export(self, records_count):
return f"Exported {records_count} records"
# Concrete method ā shared by all exporters
def validate_destination(self, destination):
if not destination:
return "No destination provided"
return f"Valid destination: {destination}"
# Concrete class 1 ā CSV Exporter
class CSVExporter(DataExporter):
def connect(self):
return "Connected to CSV data source"
def fetch_data(self):
return {"headers": ["Name", "Age", "City"], "rows": [["Alice", 30, "NYC"], ["Bob", 25, "LA"]]}
def format_data(self, data):
headers = data["headers"]
rows = data["rows"]
csv_lines = [",".join(headers)]
for row in rows:
csv_lines.append(",".join(str(item) for item in row))
return "\n".join(csv_lines)
def export(self, data, destination):
formatted = self.format_data(data)
self.log_export(len(data["rows"]))
return f"CSV exported to {destination}:\n{formatted}"
# Concrete class 2 ā JSON Exporter
class JSONExporter(DataExporter):
def connect(self):
return "Connected to JSON data source"
def fetch_data(self):
return {"users": [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]}
def format_data(self, data):
import json
return json.dumps(data, indent=2)
def export(self, data, destination):
formatted = self.format_data(data)
self.log_export(len(data["users"]))
return f"JSON exported to {destination}:\n{formatted}"
# Concrete class 3 ā Excel Exporter
class ExcelExporter(DataExporter):
def connect(self):
return "Connected to Excel data source"
def fetch_data(self):
return {"sheet": "Sheet1", "data": [["Name", "Age"], ["Alice", 30], ["Bob", 25]]}
def format_data(self, data):
sheet = data["sheet"]
rows = data["data"]
formatted = [f"Sheet: {sheet}"]
for row in rows:
formatted.append(" | ".join(str(item) for item in row))
return "\n".join(formatted)
def export(self, data, destination):
formatted = self.format_data(data)
self.log_export(len(data["data"]) - 1)
return f"Excel exported to {destination}:\n{formatted}"
# Using the exporters
print("=" * 50)
print("DATA EXPORT SYSTEM")
print("=" * 50)
def export_data(exporter, destination):
"""Function that works with any exporter (polymorphism)"""
print(exporter.connect())
data = exporter.fetch_data()
result = exporter.export(data, destination)
print(result)
print(exporter.validate_destination(destination))
print("\n=== CSV EXPORT ===")
csv_exporter = CSVExporter()
export_data(csv_exporter, "data.csv")
print("\n=== JSON EXPORT ===")
json_exporter = JSONExporter()
export_data(json_exporter, "data.json")
print("\n=== EXCEL EXPORT ===")
excel_exporter = ExcelExporter()
export_data(excel_exporter, "data.xlsx")
print("\nā
Abstract methods enforce a consistent interface across all exporters!")
print("Each exporter implements the same methods but in different ways.")
Real-world example key points:
- Abstract class ā DataExporter defines the interface (connect, fetch, format, export)
- Concrete methods ā log_export() and validate_destination() are shared
- CSVExporter ā implements abstract methods for CSV format
- JSONExporter ā implements abstract methods for JSON format
- ExcelExporter ā implements abstract methods for Excel format
- export_data() ā works with any exporter (polymorphism)
Quick Check: What does the export_data function demonstrate? (Answer: Polymorphism ā it works with any exporter class that implements the DataExporter interface)
Best Practices for Abstract Methods
Using Abstract Methods Effectively
# Best practices for abstract methods
from abc import ABC, abstractmethod
# 1. Use docstrings to document abstract methods
class Validator(ABC):
@abstractmethod
def validate(self, data):
"""
Validate the given data.
Args:
data: The data to validate
Returns:
bool: True if valid, False otherwise
Raises:
ValueError: If validation fails with detailed message
"""
pass
# 2. Keep abstract methods focused and clear
# Good ā one clear responsibility
class Parser(ABC):
@abstractmethod
def parse(self, content):
"""Parse the given content into structured data"""
pass
# Bad ā too many responsibilities
class BadParser(ABC):
@abstractmethod
def parse(self, content):
pass
@abstractmethod
def save(self, data):
pass
@abstractmethod
def format(self, data):
pass # These are different responsibilities!
# 3. Use abstract methods to define a contract
class Repository(ABC):
@abstractmethod
def get(self, id):
pass
@abstractmethod
def save(self, entity):
pass
@abstractmethod
def delete(self, id):
pass
@abstractmethod
def list_all(self):
pass
# 4. Provide meaningful error messages
class Printer(ABC):
@abstractmethod
def print_document(self, document):
"""Print a document ā must be implemented by all printer types"""
pass
class NetworkPrinter(Printer):
def print_document(self, document):
# Implementation
return f"Printing to network: {document}"
# 5. Use abstract methods with default implementations when appropriate
class Logger(ABC):
@abstractmethod
def log(self, message):
pass
def log_error(self, message):
"""Concrete method that uses log() ā can be overridden"""
self.log(f"ERROR: {message}")
# 6. Name abstract methods clearly
# Good ā clear and descriptive
class Task(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def rollback(self):
pass
# Bad ā vague names
class BadTask(ABC):
@abstractmethod
def do(self):
pass
@abstractmethod
def undo(self):
pass
# 7. Don't overuse abstract methods
# Only use when you need to enforce a contract
class SimpleClass(ABC):
# Overkill for something simple
@abstractmethod
def get_value(self):
pass # This could just be a concrete method
# 8. Test abstract method implementations
class Tester(ABC):
@abstractmethod
def run_test(self):
pass
class MyTester(Tester):
def run_test(self):
print("Test running...")
return True
# Testing
tester = MyTester()
print(tester.run_test())
Best practices summary:
- Use docstrings ā document what the abstract method should do
- Keep it focused ā each abstract method should have one responsibility
- Define a contract ā abstract methods specify what child classes must do
- Provide error messages ā make it clear what's required
- Use default implementations ā when appropriate, provide concrete methods too
- Name clearly ā use descriptive names for abstract methods
- Don't overuse ā not everything needs to be abstract
- Test implementations ā make sure child classes work correctly
Quick Check: When should you use an abstract method? (Answer: When you want to enforce that all child classes implement a specific method)
Try It Yourself
Experiment with abstract methods in the editor below.
ABSTRACT METHODS PRACTICE
========================================
1. CREATING AN ABSTRACT CLASS
2. CREATING CONCRETE CLASSES
3. USING THE CLASSES
DICTIONARY STORAGE:
Stored: user1 = Alice
Stored: user2 = Bob
Alice
Keys: ['user1', 'user2']
Deleted: user1
Keys: ['user2']
FILE STORAGE:
Stored in file data.txt: product1 = Laptop
Stored in file data.txt: product2 = Phone
Laptop
Deleted from file: product2
4. ABSTRACT METHODS IN ACTION
Both classes implement the same interface:
- store()
- retrieve()
- delete()
But each does it differently!
Abstract methods practice complete!
You've Got It!
You now understand abstract methods in Python. You know how to define them using @abstractmethod, implement them in child classes, and use them to enforce contracts.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What's the difference between an abstract method and method overriding?
Can an abstract method have a body?
Can I create an object from an abstract class?
What's a common interview question about abstract methods?
When should I use abstract methods?
Can abstract methods be static or class methods?
@abstractmethod @staticmethod def method(): pass. This enforces that child classes implement a static or class method.
Where to Go From Here
Now that you understand abstract methods, check out these related topics:
Interfaces in Python
Learn about implementing interfaces using abstract methods.
Learn More āAbstract Class vs Interface
Learn the key differences between these two concepts.
Learn More āMethod Overriding
Learn more about overriding methods in child classes.
Learn More ā