Abstraction in Python
By the Syncfusion DevTutorials team · Last updated
Hiding how something works behind what it does, abstract classes with the abc module, and abstract methods every child must implement.
Think about driving a car.
You turn the key, press a pedal, and the car moves. You do not need to know how the engine, fuel system or gears work. The controls let you use the car without learning all its inner workings.
Every driver can use a car by learning the same basic controls. The details behind those controls can differ from car to car.
That is abstraction: provide a clear way to use something without making the user understand how it works inside.
What You Will Learn
In this lesson, you will learn:
- What abstraction is and how it makes a class easier to use
- How a class can offer a simple set of methods while keeping its working details internal
- What an abstract class is and why Python will not let you create one directly
- How the
abcmodule can require child classes to provide particular methods
Start With a Working Example
Press Run.
Simple controls, complicated insides
class ReportCard:
def __init__(self, marks):
self._marks = marks
def grade(self):
average = self._average()
if average >= 80:
return "A"
if average >= 60:
return "B"
return "C"
def _average(self):
return sum(self._marks) / len(self._marks)
card = ReportCard([78, 85, 60])
print(card.grade())
The person using this class calls grade(). The method handles the averaging, so the caller does not need to know those steps.
Abstraction means providing a clear way to use something without requiring the user to know how it works inside.
Abstraction
What Is Abstraction?
Abstraction gives people a small set of clear ways to use something while keeping its internal steps free to change. For a class, these ways to use it are usually its public methods.
There are two parts:
- What the class does: the methods other code is meant to use.
- How it does it: the internal steps that make those methods work.
Benefits of Abstraction
| Benefit | What it means |
|---|---|
| Less to learn | users can learn the methods they need without reading every internal step |
| Free to change | internal code can change while the public methods stay the same |
| Fewer accidental mistakes | users are guided toward the public methods instead of changing internal details directly |
| A shared interface | different classes can offer the same methods and be used in similar ways |
You have used abstraction since lesson 1. These functions give you a simple way to work with data:
Abstraction you already use
marks = [78, 85, 60]
print(sorted(marks))
print(len("Asha"))
print(sum(marks))
sorted() arranges the values for you. You call it without needing to know the sorting steps Python uses. Python could change those steps without changing how you call sorted().
Hiding Implementation Details
If other code directly uses a class's internal values, those values become part of what that code depends on. Changing them can then break the other code.
Everything exposed
class ReportCard:
def __init__(self, marks):
self.marks = marks
self.total = sum(marks)
self.count = len(marks)
self.average = self.total / self.count
card = ReportCard([78, 85, 60])
print(card.average)
print(card.total, card.count)
Any other code can now rely on total, count or average having these names. There is another problem: these values are calculated only once, so changing the marks can make them inconsistent.
Attributes that drift apart
class ReportCard:
def __init__(self, marks):
self.marks = marks
self.total = sum(marks)
self.average = self.total / len(marks)
card = ReportCard([78, 85, 60])
card.marks.append(92)
print("Marks:", card.marks)
print("Total still says:", card.total)
print("Average still says:", round(card.average, 1))
The marks changed, but the total and average did not. They were calculated when the object was created.
A class can instead provide methods that update or calculate values when needed:
A surface that cannot go stale
class ReportCard:
def __init__(self, marks):
self._marks = list(marks)
def add(self, mark):
self._marks.append(mark)
def total(self):
return sum(self._marks)
def average(self):
return round(self.total() / len(self._marks), 1)
card = ReportCard([78, 85, 60])
print(card.average())
card.add(92)
print(card.average())
average() calculates the answer from the marks each time it is called, so the result reflects any marks added through add(). The underscore in _marks is a Python convention meaning other code should treat it as internal; it does not prevent access. list(marks) makes a separate copy, so later changes to the caller's original list do not change this report card. The list is copied when the object is created; each mark must be added through the class's methods if its average should update.
Real-World Examples
| Thing | What you see | What is hidden |
|---|---|---|
| A car | key, pedals, wheel | engine, gearbox, electronics |
| A phone | icons to tap | radios, memory, the operating system |
print() | one name | buffering, encoding, the console |
| A bank card | tap it | the account, the network, the checks |
| A kettle | one switch | element, thermostat, cut-out |
In each example, you use a small set of controls without dealing with every detail behind them. Those internal details can change while the basic controls stay familiar.
Abstract Classes
What Are Abstract Classes?
An abstract class sets out methods that its child classes must provide. It may also contain ordinary methods and shared data. Python does not let you create an object directly from a class that still has abstract methods.
A Shape is a useful example. A circle and a square are shapes with different ways to calculate area. The general category Shape does not have one specific area formula of its own.
Creating Abstract Classes
A regular class can be used to create an object, even if one of its methods does no useful work:
An ordinary base class is not abstract
class Shape:
def area(self):
pass
shape = Shape()
print(shape)
print(shape.area())
pass does nothing, so area() returns None. The class let us create a Shape, but it did not provide a usable area calculation.
One alternative is to raise an error inside the method:
Refusing by raising
class Shape:
def area(self):
raise NotImplementedError("every shape must say how to work out its area")
class Square(Shape):
pass
square = Square()
print(square.area())
This gives a more useful message when area() is called. However, Python still lets us create Square; the problem appears only if code later calls the missing method.
Abstract Base Classes
An abstract base class makes Python reject an object if its class has not provided all required abstract methods. The problem is caught when the object is created.
The abc Module
The abc module provides tools for abstract base classes. To make one, inherit from ABC and mark each required method with @abstractmethod.
An abstract class refuses to be instantiated
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
shape = Shape()
Python raises a TypeError because Shape still has no implementation of area(). The exact wording of the message can vary between Python versions.
A child that implements the method works
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
for shape in [Square(4), Rectangle(3, 5)]:
print(type(shape).__name__, "area:", shape.area())
An abstract class can also have ordinary methods. Child classes inherit and can use those methods as usual:
Abstract and ordinary methods together
from abc import ABC, abstractmethod
class Shape(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def area(self):
pass
def describe(self):
return f"{self.name} has an area of {self.area()}"
class Square(Shape):
def __init__(self, side):
super().__init__("Square")
self.side = side
def area(self):
return self.side ** 2
print(Square(4).describe())
describe() is defined once in Shape. When it calls self.area(), Python runs the area() method provided by the particular child object. This lets the parent share common work while each child supplies its own area calculation.
Both parts are needed for this pattern: inherit from ABC and mark the required method with @abstractmethod. Without ABC, Python does not use the abstract-method marker to prevent object creation. Without @abstractmethod, no method is marked as required.
Abstract Methods
What Are Abstract Methods?
An abstract method is a method a parent class marks as required. A concrete child class must provide an implementation before Python will let you create its objects.
Defining Abstract Methods
Several required methods
from abc import ABC, abstractmethod
class Report(ABC):
@abstractmethod
def heading(self):
pass
@abstractmethod
def body(self):
pass
def render(self):
return self.heading() + "\n" + self.body()
class MarkReport(Report):
def __init__(self, name, marks):
self.name = name
self.marks = marks
def heading(self):
return f"MARKS - {self.name}"
def body(self):
return ", ".join(str(mark) for mark in self.marks)
print(MarkReport("Asha", [78, 85]).render())
An abstract method often uses pass as its body because the child supplies the working version. A child can still call a useful body from an abstract parent method with super(), as shown later.
Implementing Abstract Methods
A concrete child must implement every abstract method it inherits. If it does not, Python treats the child as abstract too:
One method missing is enough
from abc import ABC, abstractmethod
class Report(ABC):
@abstractmethod
def heading(self):
pass
@abstractmethod
def body(self):
pass
class HalfReport(Report):
def heading(self):
return "MARKS"
HalfReport()
The error names the missing method. Python raises it when someone tries to create a HalfReport object, rather than waiting for a later call to that method.
A child that implements none of the required methods is still an abstract class. This can be useful when it supplies part of the behavior for classes below it:
An abstract class in the middle
from abc import ABC, abstractmethod
class Report(ABC):
@abstractmethod
def heading(self):
pass
@abstractmethod
def body(self):
pass
class SchoolReport(Report):
def heading(self):
return "GREEN VALLEY SCHOOL"
class MarkReport(SchoolReport):
def __init__(self, marks):
self.marks = marks
def body(self):
return f"Marks: {self.marks}"
report = MarkReport([78, 85])
print(report.heading())
print(report.body())
SchoolReport supplies heading() and leaves body() for a more specific child class to implement.
An abstract method can also have a useful body. A child can call that body with super():
An abstract method with something in it
from abc import ABC, abstractmethod
class Report(ABC):
@abstractmethod
def heading(self):
return "REPORT"
class MarkReport(Report):
def heading(self):
return super().heading() + " - MARKS"
print(MarkReport().heading())
The child must still define heading(). The abstract marker requires the child to provide its own method, even though it can reuse the parent's implementation.
Make a class abstract when it describes a category, such as Shape, Report or Payment, but should not be used to create an object by itself. If the parent is useful as an object on its own, a regular base class may be enough.
| Ordinary base class | Abstract base class | |
|---|---|---|
| Can you create an object? | yes | no, while required methods are missing |
| Required methods | nothing enforced | @abstractmethod is enforced through ABC |
| When a missing method is found | when it is called, if ever | when you try to create an object |
| Use when | the parent is useful on its own | the parent describes a category and requires methods from children |
Why the timing matters
from abc import ABC, abstractmethod
class LooseShape:
def area(self):
raise NotImplementedError
class StrictShape(ABC):
@abstractmethod
def area(self):
pass
class LooseSquare(LooseShape):
pass
class StrictSquare(StrictShape):
pass
loose = LooseSquare()
print("The loose object was created with no complaint:", loose)
try:
StrictSquare()
except TypeError as error:
print("The strict one was refused:", error)
Python creates the LooseSquare object; its area() method will raise an error only if called. Python refuses to create StrictSquare because it has not implemented the abstract area() method.
Common mistakes
Each row shows a mistake, what appears, why it happens, and how to fix it.
| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
Forgetting to inherit from ABC | The class can still be instantiated | The marker is not enforced by the class's metaclass | Inherit from ABC |
Forgetting @abstractmethod | A child can be created without defining the method | No method was marked as required | Add the decorator |
| Implementing only some abstract methods | TypeError when creating the child object | Every required method must be implemented | Implement them all, or keep the child abstract |
| Misspelling the method in the child | TypeError about the abstract method | The name must match exactly | Check the name against the parent |
| Trying to create an abstract class with required methods | TypeError when creating the object | The required methods have no implementation | Create a concrete child instead |
| Making every method abstract | Child classes repeat the same shared code | No shared implementation is available | Keep common behavior in ordinary parent methods |
| Saving worked-out values as attributes | Values no longer match after data changes | They were calculated once in __init__ | Calculate them in a method when needed |
| Keeping the caller's list directly | Later changes to the caller's list change the report card | Both names refer to the same list | Copy it with list(marks) in the constructor |
What to Remember
- Abstraction means providing a clear way to use something without requiring the user to know how it works inside.
- A small public interface can make a class easier to learn and leave its internal code free to change.
- A value calculated once in the constructor does not update when the data changes. A method can calculate it when asked.
- Copy a list you receive with
list(...)if later changes to the original list should not affect your object. - An abstract class with unimplemented abstract methods cannot be used to create an object.
- To require methods, inherit from
ABCand mark them with@abstractmethod. - A concrete child must implement every abstract method it inherits. A child that does not is abstract too.
- Python reports a missing implementation when you try to create an object, rather than waiting for a later method call.
- An abstract class can also contain ordinary methods, and those methods can call the required methods implemented by child classes.
Next: Encapsulation